Initial commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:02:30 +07:00
commit 0a84b9d75e
15481 changed files with 2009655 additions and 0 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: fa423365b1ce06a4dbdc6fb4a8597bfa
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 891bb6b9f5094c36b59c3788e21c8213
timeCreated: 1638532925

View File

@@ -0,0 +1,162 @@
#if !UNITY_2023_2_OR_NEWER
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using NUnit.Framework.Interfaces;
using UnityEditor.TestTools.TestRunner.TestRun;
using UnityEngine;
using UnityEngine.TestTools;
namespace UnityEditor.TestTools.TestRunner.Api.Analytics
{
internal static class AnalyticsReporter
{
private const string VendorKey = "unity.test-framework";
private const string RunFinishedEventName = "runFinished";
private const string AnalyzeTestTreeName = "analyzeTestTree";
private static bool isSetUp;
private static IDictionary<string, bool> methodsAnalyzed;
private static IDictionary<string, bool> typesAnalyzed;
private static void SetUpIfNeeded()
{
if (isSetUp)
{
return;
}
isSetUp = true;
EditorAnalytics.RegisterEventWithLimit(RunFinishedEventName, 60, 30, VendorKey);
EditorAnalytics.RegisterEventWithLimit(AnalyzeTestTreeName, 3, 30, VendorKey);
}
[InitializeOnLoadMethod]
private static void RegisterCallbacks()
{
ScriptableObject.CreateInstance<TestRunnerApi>().RegisterCallbacks(new AnalyticsTestCallback(ReportRunFinished));
}
private static void ReportRunFinished(ITestResultAdaptor testResult)
{
SetUpIfNeeded();
var activeRuns = TestJobDataHolder.instance.TestRuns;
if (activeRuns.Count == 0)
{
return;
}
var executionSettings = activeRuns[0].executionSettings;
var filter = executionSettings.filters.First();
var runFinishedData = new RunFinishedData
{
totalTests = testResult.Test.TestCaseCount,
numPassedTests = testResult.PassCount,
numFailedTests = testResult.FailCount,
numInconclusiveTests = testResult.InconclusiveCount,
numSkippedTests = testResult.SkipCount,
testModeFilter = (int)filter.testMode,
targetPlatform = executionSettings.targetPlatform != null ? executionSettings.targetPlatform.ToString() : "editor",
runSynchronously = executionSettings.runSynchronously,
isCustomRunner = false,
isFiltering = executionSettings.filters.Any(f => f.HasAny()),
isAutomated = IsCommandLineArgSet("-automated"),
isFromCommandLine = IsCommandLineArgSet("-runTests"),
totalTestDuration = testResult.Duration,
totalRunDuration = (DateTime.Now - Convert.ToDateTime(activeRuns[0].startTime)).TotalSeconds
};
EditorAnalytics.SendEventWithLimit(RunFinishedEventName, runFinishedData, 1);
}
private static bool IsCommandLineArgSet(string command)
{
return Environment.GetCommandLineArgs().Any(c => c == command);
}
internal static void AnalyzeTestTreeAndReport(ITest testTree)
{
SetUpIfNeeded();
typesAnalyzed = new Dictionary<string, bool>();
methodsAnalyzed = new Dictionary<string, bool>();
var data = new TestTreeData();
AnalyzeTestTreeNode(testTree, data);
EditorAnalytics.SendEventWithLimit(AnalyzeTestTreeName, data, 1);
}
private static void AnalyzeTestTreeNode(ITest node, TestTreeData data)
{
var attributes = GetAttributes(node).ToArray();
if (attributes.OfType<TestAttribute>().Any())
{
data.numTestAttributes++;
}
if (attributes.OfType<UnityTestAttribute>().Any())
{
data.numUnityTestAttributes++;
}
if (attributes.OfType<CategoryAttribute>().Any())
{
data.numCategoryAttributes++;
}
if (attributes.OfType<TestFixtureAttribute>().Any())
{
data.numTestFixtureAttributes++;
}
if (attributes.OfType<ConditionalIgnoreAttribute>().Any())
{
data.numConditionalIgnoreAttributes++;
}
if (attributes.OfType<UnityPlatformAttribute>().Any())
{
data.numUnityPlatformAttributes++;
}
if (node.HasChildren)
{
foreach (var test in node.Tests)
{
AnalyzeTestTreeNode(test, data);
}
}
else
{
data.totalNumberOfTests++;
}
}
private static IEnumerable<NUnitAttribute> GetAttributes(ITest node)
{
if (node.Method != null)
{
var key = $"{node.MethodName},{node.ClassName}";
if (methodsAnalyzed.ContainsKey(key))
{
yield break;
}
methodsAnalyzed[key] = true;
foreach (var attribute in (node).Method.GetCustomAttributes<NUnitAttribute>(true))
{
yield return attribute;
}
var typeKey = node.Method.TypeInfo.FullName;
if (typesAnalyzed.ContainsKey(typeKey))
{
yield break;
}
typesAnalyzed[typeKey] = true;
foreach (var attribute in node.Method.TypeInfo.GetCustomAttributes<NUnitAttribute>(true))
{
yield return attribute;
}
}
}
}
}
#endif

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a85430cc5a4a4279a992be322de12b29
timeCreated: 1638532946

View File

@@ -0,0 +1,31 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api.Analytics
{
internal class AnalyticsTestCallback : ICallbacks
{
private Action<ITestResultAdaptor> _runFinishedCallback;
public AnalyticsTestCallback(Action<ITestResultAdaptor> runFinishedCallback)
{
_runFinishedCallback = runFinishedCallback;
}
public void RunStarted(ITestAdaptor testsToRun)
{
}
public void RunFinished(ITestResultAdaptor result)
{
_runFinishedCallback(result);
}
public void TestStarted(ITestAdaptor test)
{
}
public void TestFinished(ITestResultAdaptor result)
{
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: f5e82966bb8646269e2e46b6ddf2d89f
timeCreated: 1638535350

View File

@@ -0,0 +1,22 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api.Analytics
{
internal class RunFinishedData
{
public int totalTests;
public int numPassedTests;
public int numFailedTests;
public int numInconclusiveTests;
public int numSkippedTests;
public int testModeFilter;
public bool isAutomated;
public bool isFromCommandLine;
public bool isFiltering;
public string targetPlatform;
public double totalTestDuration;
public double totalRunDuration;
public bool runSynchronously;
public bool isCustomRunner;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: dc781c79a6ac490f817bb70a01490d5c
timeCreated: 1638533438

View File

@@ -0,0 +1,17 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api.Analytics
{
internal class TestTreeData
{
public int totalNumberOfTests;
public int numTestAttributes;
public int numUnityTestAttributes;
public int numCategoryAttributes;
public int numTestFixtureAttributes;
public int numConditionalIgnoreAttributes;
public int numRequiresPlayModeAttributesTrue;
public int numRequiresPlayModeAttributesFalse;
public int numUnityPlatformAttributes;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 697ab794770540d6951f83d62b8fa444
timeCreated: 1639043038

View File

@@ -0,0 +1,149 @@
using System;
using System.Linq;
using System.Text;
using NUnit.Framework.Interfaces;
using Unity.Profiling;
using UnityEngine;
using UnityEngine.TestRunner.TestLaunchers;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal class CallbacksDelegator : ICallbacksDelegator
{
private static CallbacksDelegator s_instance;
public static CallbacksDelegator instance
{
get
{
if (s_instance == null)
{
s_instance = new CallbacksDelegator(CallbacksHolder.instance.GetAll, new TestAdaptorFactory());
}
return s_instance;
}
}
private readonly Func<ICallbacks[]> m_CallbacksProvider;
private readonly ITestAdaptorFactory m_AdaptorFactory;
// Note that in the event of a domain reload the filter is not reapplied and will be null
private ITestFilter m_TestRunFilter;
public CallbacksDelegator(Func<ICallbacks[]> callbacksProvider, ITestAdaptorFactory adaptorFactory)
{
m_CallbacksProvider = callbacksProvider;
m_AdaptorFactory = adaptorFactory;
}
public void RunStarted(ITest testsToRun)
{
m_AdaptorFactory.ClearResultsCache();
var testRunnerTestsToRun = m_AdaptorFactory.Create(testsToRun, m_TestRunFilter);
TryInvokeAllCallbacks(callbacks => callbacks.RunStarted(testRunnerTestsToRun));
}
public void RunStartedRemotely(byte[] testsToRunData)
{
var testData = Deserialize<RemoteTestResultDataWithTestData>(testsToRunData);
var testsToRun = m_AdaptorFactory.BuildTree(testData);
TryInvokeAllCallbacks(callbacks => callbacks.RunStarted(testsToRun));
}
public void RunFinished(ITestResult testResults)
{
var testResult = m_AdaptorFactory.Create(testResults);
TryInvokeAllCallbacks(callbacks => callbacks.RunFinished(testResult));
}
public void RunFinishedRemotely(byte[] testResultsData)
{
var remoteTestResult = Deserialize<RemoteTestResultDataWithTestData>(testResultsData);
var testResult = m_AdaptorFactory.Create(remoteTestResult.results.First(), remoteTestResult);
TryInvokeAllCallbacks(callbacks => callbacks.RunFinished(testResult));
}
public void RunFailed(string failureMessage)
{
Debug.LogError(failureMessage);
TryInvokeAllCallbacks(callbacks =>
{
var errorCallback = callbacks as IErrorCallbacks;
if (errorCallback != null)
{
errorCallback.OnError(failureMessage);
}
});
}
public void TestStarted(ITest test)
{
var testRunnerTest = m_AdaptorFactory.Create(test);
TryInvokeAllCallbacks(callbacks => callbacks.TestStarted(testRunnerTest));
}
public void TestStartedRemotely(byte[] testStartedData)
{
var testData = Deserialize<RemoteTestResultDataWithTestData>(testStartedData);
var testsToRun = m_AdaptorFactory.BuildTree(testData);
TryInvokeAllCallbacks(callbacks => callbacks.TestStarted(testsToRun));
}
public void TestFinished(ITestResult result)
{
var testResult = m_AdaptorFactory.Create(result);
TryInvokeAllCallbacks(callbacks => callbacks.TestFinished(testResult));
}
public void TestFinishedRemotely(byte[] testResultsData)
{
var remoteTestResult = Deserialize<RemoteTestResultDataWithTestData>(testResultsData);
var testResult = m_AdaptorFactory.Create(remoteTestResult.results.First(), remoteTestResult);
TryInvokeAllCallbacks(callbacks => callbacks.TestFinished(testResult));
}
public void TestTreeRebuild(ITest test)
{
using (new ProfilerMarker(nameof(TestTreeRebuild)).Auto())
{
m_AdaptorFactory.ClearTestsCache();
ITestAdaptor testAdaptor;
using (new ProfilerMarker("CreateTestAdaptors").Auto())
testAdaptor = m_AdaptorFactory.Create(test);
TryInvokeAllCallbacks(callbacks =>
{
var rebuildCallbacks = callbacks as ITestTreeRebuildCallbacks;
if (rebuildCallbacks != null)
{
rebuildCallbacks.TestTreeRebuild(testAdaptor);
}
});
}
}
public void SetTestRunFilter(ITestFilter filter)
{
m_TestRunFilter = filter;
}
private void TryInvokeAllCallbacks(Action<ICallbacks> callbackAction)
{
foreach (var testRunnerApiCallback in m_CallbacksProvider())
{
try
{
callbackAction(testRunnerApiCallback);
}
catch (Exception ex)
{
Debug.LogException(ex);
}
}
}
private static T Deserialize<T>(byte[] data)
{
return JsonUtility.FromJson<T>(Encoding.UTF8.GetString(data));
}
}
}

View File

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

View File

@@ -0,0 +1,69 @@
using System;
using System.Collections.Generic;
using System.Linq;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal class CallbacksHolder : ScriptableSingleton<CallbacksHolder>, ICallbacksHolder
{
private List<CallbackWithPriority> m_Callbacks = new List<CallbackWithPriority>();
public void Add(ICallbacks callback, int priority)
{
m_Callbacks.Add(new CallbackWithPriority(callback, priority));
}
public void Remove(ICallbacks callback)
{
m_Callbacks.RemoveAll(callbackWithPriority => callbackWithPriority.Callback == callback);
}
public ICallbacks[] GetAll()
{
return m_Callbacks.OrderByDescending(callback => callback.Priority).Select(callback => callback.Callback).ToArray();
}
public void Clear()
{
m_Callbacks.Clear();
}
private struct CallbackWithPriority
{
public ICallbacks Callback;
public int Priority;
public CallbackWithPriority(ICallbacks callback, int priority)
{
Callback = callback;
Priority = priority;
}
}
// Sometimes - such as when we want to test the test framework itself - it's necessary to launch a test run from
// inside a test. Because callbacks are registered globally, this can cause a lot of confusion (e.g. the in-test
// run will emit UTP messages, utterly confusing UTR). In such circumstances the safest thing to do is to
// temporarily suppress all registered callbacks for the duration of the in-test run. This method can be called
// to set up a using() block which will suppress the callbacks for the scope.
public IDisposable TemporarilySuppressCallbacks()
{
return new Suppressor(this);
}
private sealed class Suppressor : IDisposable
{
private readonly CallbacksHolder _instance;
private readonly List<CallbackWithPriority> _suppressed;
public Suppressor(CallbacksHolder instance)
{
_instance = instance;
_suppressed = new List<CallbackWithPriority>(instance.m_Callbacks);
instance.m_Callbacks.Clear();
}
public void Dispose()
{
_instance.m_Callbacks.AddRange(_suppressed);
}
}
}
}

View File

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

View File

@@ -0,0 +1,182 @@
using System;
using System.Linq;
using System.Text;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal.Filters;
using UnityEngine;
using UnityEngine.Serialization;
using UnityEngine.TestRunner.NUnitExtensions.Runner;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// A set of execution settings defining how to run tests, using the <see cref="TestRunnerApi"/>.
/// </summary>
[Serializable]
public class ExecutionSettings
{
/// <summary>
/// Creates an instance with a given set of filters, if any.
/// </summary>
/// <param name="filtersToExecute">Set of filters</param>
public ExecutionSettings(params Filter[] filtersToExecute)
{
filters = filtersToExecute;
}
[SerializeField]
private BuildTarget m_TargetPlatform;
[SerializeField]
private bool m_HasTargetPlatform;
/// <summary>
/// An instance of <see cref="ITestRunSettings"/> to set up before running tests on a Player.
/// </summary>
// Note: Is not available after serialization
public ITestRunSettings overloadTestRunSettings;
[SerializeField]
internal Filter filter;
///<summary>
///A collection of <see cref="Filter"/> to execute tests on.
///</summary>
[SerializeField]
public Filter[] filters;
/// <summary>
/// Note that this is only supported for EditMode tests, and that tests which take multiple frames (i.e. [UnityTest] tests, or tests with [UnitySetUp] or [UnityTearDown] scaffolding) will be filtered out.
/// </summary>
/// <value>If true, the call to Execute() will run tests synchronously, guaranteeing that all tests have finished running by the time the call returns.</value>
[SerializeField]
public bool runSynchronously;
/// <summary>
/// The time, in seconds, the editor should wait for heartbeats after starting a test run on a player. This defaults to 10 minutes.
/// </summary>
[SerializeField]
public int playerHeartbeatTimeout = 60 * 10;
[SerializeField]
internal string[] orderedTestNames;
[SerializeField]
internal IgnoreTest[] ignoreTests;
[SerializeField]
internal FeatureFlags featureFlags;
[SerializeField]
internal int randomOrderSeed;
internal string playerSavePath { get; set; }
internal int retryCount { get; set; }
internal int repeatCount { get; set; }
internal bool EditModeIncluded()
{
return filters.Any(f => IncludesTestMode(f.testMode, TestMode.EditMode));
}
internal bool PlayModeInEditorIncluded()
{
return filters.Any(f => IncludesTestMode(f.testMode, TestMode.PlayMode) && targetPlatform == null);
}
internal bool PlayerIncluded()
{
return filters.Any(f => IncludesTestMode(f.testMode, TestMode.PlayMode) && targetPlatform != null);
}
private static bool IncludesTestMode(TestMode testMode, TestMode modeToCheckFor)
{
return (testMode & modeToCheckFor) == modeToCheckFor;
}
internal ITestFilter BuildNUnitFilter()
{
return new OrFilter(filters.Select(f => f.ToRuntimeTestRunnerFilter(runSynchronously).BuildNUnitFilter()).ToArray());
}
/// <summary>
/// The <see cref="BuildTarget"/> platform to run the test on. If set to null, then the Editor is the target for the tests.
/// </summary>
internal BuildTarget? targetPlatform
{
get { return m_HasTargetPlatform ? (BuildTarget?)m_TargetPlatform : null; }
set
{
{
if (value.HasValue)
{
m_HasTargetPlatform = true;
m_TargetPlatform = value.Value;
}
else
{
m_HasTargetPlatform = false;
m_TargetPlatform = default;
}
}
}
}
/// <summary>
/// Implementation of ToString() that builds a string composed of the execution settings.
/// </summary>
/// <returns>The current execution settings as a string.</returns>
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"{nameof(ExecutionSettings)} with details:");
stringBuilder.AppendLine($"{nameof(targetPlatform)} = {targetPlatform}");
stringBuilder.AppendLine($"{nameof(playerHeartbeatTimeout)} = {playerHeartbeatTimeout}");
if (filters.Length == 0)
{
stringBuilder.AppendLine($"{nameof(filters)} = {{}}");
}
for (int i = 0; i < filters.Length; i++)
{
stringBuilder.AppendLine($"{nameof(filters)}[{i}] = ");
var filterStrings = filters[i]
.ToString()
.Split(new[] {Environment.NewLine}, StringSplitOptions.RemoveEmptyEntries)
.ToArray();
foreach (var filterString in filterStrings)
{
stringBuilder.AppendLine($" {filterString}");
}
}
if (ignoreTests == null || ignoreTests.Length == 0)
{
stringBuilder.AppendLine($"{nameof(ignoreTests)} = {{}}");
}
else
{
for (int i = 0; i < ignoreTests.Length; i++)
{
stringBuilder.AppendLine($"{nameof(ignoreTests)}[{i}] = {ignoreTests[i]}");
}
}
if (featureFlags == null)
{
stringBuilder.AppendLine($"{nameof(featureFlags)} = null");
}
else
{
stringBuilder.AppendLine("Feature Flags:");
stringBuilder.AppendLine($" {nameof(featureFlags.fileCleanUpCheck)} = {featureFlags.fileCleanUpCheck}");
stringBuilder.AppendLine($" {nameof(featureFlags.requiresSplashScreen)} = {featureFlags.requiresSplashScreen}");
stringBuilder.AppendLine($" {nameof(featureFlags.strictDomainReload)} = {featureFlags.strictDomainReload}");
stringBuilder.AppendLine($" {nameof(featureFlags.disableNestedEnumeratorBugfix)} = {featureFlags.disableNestedEnumeratorBugfix}");
}
stringBuilder.AppendLine();
return stringBuilder.ToString();
}
}
}

View File

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

View File

@@ -0,0 +1,108 @@
using System;
using System.Linq;
using System.Text;
using UnityEngine;
using UnityEngine.TestTools;
using UnityEngine.TestTools.TestRunner.GUI;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// The filter class provides the <see cref="TestRunnerApi"/> with a specification of what tests to run when [running tests programmatically](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/extension-run-tests.html).
/// </summary>
[Serializable]
public class Filter
{
/// <summary>
/// An enum flag that specifies if Edit Mode or Play Mode tests should run.
/// </summary>
[SerializeField]
public TestMode testMode;
/// <summary>
/// The full name of the tests to match the filter. This is usually in the format FixtureName.TestName. If the test has test arguments, then include them in parenthesis. E.g. MyTestClass2.MyTestWithMultipleValues(1).
/// </summary>
[SerializeField]
public string[] testNames;
/// <summary>
/// The same as testNames, except that it allows for Regex. This is useful for running specific fixtures or namespaces. E.g. "^MyNamespace\\." Runs any tests where the top namespace is MyNamespace.
/// </summary>
[SerializeField]
public string[] groupNames;
/// <summary>
/// The name of a [Category](https://nunit.org/docs/2.2.7/category.html) to include in the run. Any test or fixtures runs that have a Category matching the string.
/// </summary>
[SerializeField]
public string[] categoryNames;
/// <summary>
/// The name of assemblies included in the run. That is the assembly name, without the .dll file extension. E.g., MyTestAssembly
/// </summary>
[SerializeField]
public string[] assemblyNames;
/// <summary>
/// The <see cref="BuildTarget"/> platform to run the test on. If set to null, then the Editor is the target for the tests.
/// Obsolete. Use the targetPlatform property on the <see cref="ExecutionSettings"/>.
/// </summary>
public BuildTarget? targetPlatform;
/// <summary>
/// Implementation of ToString() that builds a string composed of the filter values.
/// </summary>
/// <returns>The current filter values as a string.</returns>
public override string ToString()
{
var stringBuilder = new StringBuilder();
stringBuilder.AppendLine($"{nameof(Filter)} with settings:");
stringBuilder.AppendLine($"{nameof(testMode)} = {testMode}");
stringBuilder.AppendLine($"{nameof(targetPlatform)} = {targetPlatform}");
stringBuilder.AppendLine($"{nameof(testNames)} = " + (testNames == null ? "null" : string.Join(", ", testNames)));
stringBuilder.AppendLine($"{nameof(groupNames)} = " + (groupNames == null ? "null" : string.Join(", ", groupNames)));
stringBuilder.AppendLine($"{nameof(categoryNames)} = " + (categoryNames == null ? "null" : string.Join(", ", categoryNames)));
stringBuilder.AppendLine($"{nameof(assemblyNames)} = " + (assemblyNames == null ? "null" : string.Join(", ", assemblyNames)));
return stringBuilder.ToString();
}
internal RuntimeTestRunnerFilter ToRuntimeTestRunnerFilter(bool synchronousOnly)
{
return new RuntimeTestRunnerFilter
{
testMode = ConvertTestMode(testMode),
testNames = testNames,
categoryNames = categoryNames,
groupNames = groupNames,
assemblyNames = assemblyNames,
synchronousOnly = synchronousOnly
};
}
private static TestPlatform ConvertTestMode(TestMode testMode)
{
if (testMode == (TestMode.EditMode | TestMode.PlayMode))
{
return TestPlatform.All;
}
if (testMode == TestMode.EditMode)
{
return TestPlatform.EditMode;
}
if (testMode == TestMode.PlayMode)
{
return TestPlatform.PlayMode;
}
return 0;
}
internal bool HasAny()
{
return assemblyNames != null && assemblyNames.Any()
|| categoryNames != null && categoryNames.Any()
|| groupNames != null && groupNames.Any()
|| testNames != null && testNames.Any();
}
}
}

View File

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

View File

@@ -0,0 +1,31 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// Callbacks in the <see cref="TestRunnerApi"/> for the test stages when running tests.
/// </summary>
public interface ICallbacks
{
/// <summary>
/// A callback invoked when a test run is started.
/// </summary>
/// <param name="testsToRun">The full loaded test tree.</param>
void RunStarted(ITestAdaptor testsToRun);
/// <summary>
/// A callback invoked when a test run is finished.
/// </summary>
/// <param name="result">The result of the test run.</param>
void RunFinished(ITestResultAdaptor result);
/// <summary>
/// A callback invoked when each individual node of the test tree has started executing.
/// </summary>
/// <param name="test">The test node currently executed.</param>
void TestStarted(ITestAdaptor test);
/// <summary>
/// A callback invoked when each individual node of the test tree has finished executing.
/// </summary>
/// <param name="result">The result of the test tree node after it had been executed.</param>
void TestFinished(ITestResultAdaptor result);
}
}

View File

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

View File

@@ -0,0 +1,20 @@
using System;
using NUnit.Framework.Interfaces;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal interface ICallbacksDelegator
{
void RunStarted(ITest testsToRun);
void RunStartedRemotely(byte[] testsToRunData);
void RunFinished(ITestResult testResults);
void RunFinishedRemotely(byte[] testResultsData);
void RunFailed(string failureMessage);
void TestStarted(ITest test);
void TestStartedRemotely(byte[] testStartedData);
void TestFinished(ITestResult result);
void TestFinishedRemotely(byte[] testResultsData);
void TestTreeRebuild(ITest test);
void SetTestRunFilter(ITestFilter filter);
}
}

View File

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

View File

@@ -0,0 +1,12 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal interface ICallbacksHolder
{
void Add(ICallbacks callback, int priority);
void Remove(ICallbacks callback);
ICallbacks[] GetAll();
void Clear();
}
}

View File

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

View File

@@ -0,0 +1,18 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// An extended version of the <see cref="ICallbacks"/>, which get invoked if the test run fails due to a build error or if any <see cref="UnityEngine.TestTools.IPrebuildSetup"/> has a failure.
/// </summary>
public interface IErrorCallbacks : ICallbacks
{
/// <summary>
/// Method invoked on failure.
/// </summary>
/// <param name="message">
/// The error message detailing the reason for the run to fail.
/// </param>
void OnError(string message);
}
}

View File

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

View File

@@ -0,0 +1,105 @@
using System;
using System.Collections.Generic;
using NUnit.Framework.Interfaces;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// ```ITestAdaptor``` is a representation of a node in the test tree implemented as a wrapper around the [NUnit](http://www.nunit.org/) [ITest](https://github.com/nunit/nunit/blob/master/src/NUnitFramework/framework/Interfaces/ITest.cs) interface.
/// </summary>
public interface ITestAdaptor
{
/// <summary>
/// The ID of the test tree node. The ID can change if you add new tests to the suite. Use UniqueName, if you want to have a more permanent point of reference.
/// </summary>
string Id { get; }
/// <summary>
/// The name of the test. E.g.,```MyTest```.
/// </summary>
string Name { get; }
/// <summary>
/// The full name of the test. E.g., ```MyNamespace.MyTestClass.MyTest```.
/// </summary>
string FullName { get; }
/// <summary>
/// The total number of test cases in the node and all sub-nodes.
/// </summary>
int TestCaseCount { get; }
/// <summary>
/// Whether the node has any children.
/// </summary>
bool HasChildren { get; }
/// <summary>
/// True if the node is a test suite/fixture, false otherwise.
/// </summary>
bool IsSuite { get; }
/// <summary>
/// The child nodes.
/// </summary>
IEnumerable<ITestAdaptor> Children { get; }
/// <summary>
/// The parent node, if any.
/// </summary>
ITestAdaptor Parent { get; }
/// <summary>
/// The test case timeout in milliseconds. Note that this value is only available on TestFinished.
/// </summary>
int TestCaseTimeout { get; }
/// <summary>
/// The type of test class as an ```NUnit``` <see cref="ITypeInfo"/>. If the node is not a test class, then the value is null.
/// </summary>
ITypeInfo TypeInfo { get; }
/// <summary>
/// The Nunit <see cref="IMethodInfo"/> of the test method. If the node is not a test method, then the value is null.
/// </summary>
IMethodInfo Method { get; }
/// <summary>
/// The array of arguments that the test method/fixture will be invoked with.
/// </summary>
object[] Arguments { get; }
/// <summary>
/// An array of the categories applied to the test or fixture.
/// </summary>
string[] Categories { get; }
/// <summary>
/// Returns true if the node represents a test assembly, false otherwise.
/// </summary>
bool IsTestAssembly { get; }
/// <summary>
/// The run state of the test node. Either ```NotRunnable```, ```Runnable```, ```Explicit```, ```Skipped```, or ```Ignored```.
/// </summary>
RunState RunState { get; }
/// <summary>
/// The description of the test.
/// </summary>
string Description { get; }
/// <summary>
/// The skip reason. E.g., if ignoring the test.
/// </summary>
string SkipReason { get; }
/// <summary>
/// The ID of the parent node.
/// </summary>
string ParentId { get; }
/// <summary>
/// The full name of the parent node.
/// </summary>
string ParentFullName { get; }
/// <summary>
/// A unique generated name for the test node. E.g., ```Tests.dll/MyNamespace/MyTestClass/[Tests][MyNamespace.MyTestClass.MyTest]```.
/// </summary>
string UniqueName { get; }
/// <summary>
/// A unique name of the parent node. E.g., ```Tests.dll/MyNamespace/[Tests][MyNamespace.MyTestClass][suite]```.
/// </summary>
string ParentUniqueName { get; }
/// <summary>
/// The child index of the node in its parent.
/// </summary>
int ChildIndex { get; }
/// <summary>
/// The mode of the test. Either **Edit Mode** or **Play Mode**.
/// </summary>
TestMode TestMode { get; }
}
}

View File

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

View File

@@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using NUnit.Framework.Interfaces;
using UnityEngine.TestRunner.TestLaunchers;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal interface ITestAdaptorFactory
{
ITestAdaptor Create(ITest test);
ITestAdaptor Create(RemoteTestData testData);
ITestResultAdaptor Create(ITestResult testResult);
ITestAdaptor Create(ITest test, ITestFilter filter);
ITestResultAdaptor Create(RemoteTestResultData testResult, RemoteTestResultDataWithTestData allData);
ITestAdaptor BuildTree(RemoteTestResultDataWithTestData data);
IEnumerator<ITestAdaptor> BuildTreeAsync(RemoteTestResultDataWithTestData data);
void ClearResultsCache();
void ClearTestsCache();
}
}

View File

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

View File

@@ -0,0 +1,121 @@
using System;
using System.Collections.Generic;
using NUnit.Framework.Interfaces;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// The `ITestResultAdaptor` is the representation of the test results for a node in the test tree implemented as a wrapper around the [NUnit](http://www.nunit.org/) [ITest](https://github.com/nunit/nunit/blob/master/src/NUnitFramework/framework/Interfaces/ITestResults.cs) interface.
/// </summary>
public interface ITestResultAdaptor
{
/// <summary>
/// The test details of the test result tree node as a <see cref="TestAdaptor"/>
/// </summary>
ITestAdaptor Test { get; }
///<summary>
///The name of the test node.
///</summary>
string Name { get; }
/// <summary>
/// Gets the full name of the test result
/// </summary>
///<value>
///The name of the test result.
///</value>
string FullName { get; }
///<summary>
///Gets the state of the result as a string.
///</summary>
///<value>
///It returns one of these values: `Inconclusive`, `Skipped`, `Skipped:Ignored`, `Skipped:Explicit`, `Passed`, `Failed`, `Failed:Error`, `Failed:Cancelled`, `Failed:Invalid`
///</value>
string ResultState { get; }
///<summary>
///Gets the status of the test as an enum.
///</summary>
///<value>
///It returns one of these values:`Inconclusive`, `Skipped`, `Passed`, or `Failed`
///</value>
TestStatus TestStatus { get; }
/// <summary>
/// Gets the elapsed time for running the test in seconds
/// </summary>
/// <value>
/// Time in seconds.
/// </value>
double Duration { get; }
/// <summary>
/// Gets or sets the time the test started running.
/// </summary>
///<value>
///A DataTime object.
///</value>
DateTime StartTime { get; }
///<summary>
///Gets or sets the time the test finished running.
///</summary>
///<value>
///A DataTime object.
///</value>
DateTime EndTime { get; }
/// <summary>
/// The message associated with a test failure or with not running the test
/// </summary>
string Message { get; }
/// <summary>
/// Any stacktrace associated with an error or failure. Not available in the Compact Framework 1.0.
/// </summary>
string StackTrace { get; }
/// <summary>
/// The number of asserts executed when running the test and all its children.
/// </summary>
int AssertCount { get; }
/// <summary>
/// The number of test cases that failed when running the test and all its children.
/// </summary>
int FailCount { get; }
/// <summary>
/// The number of test cases that passed when running the test and all its children.
/// </summary>
int PassCount { get; }
/// <summary>
/// The number of test cases that were skipped when running the test and all its children.
/// </summary>
int SkipCount { get; }
/// <summary>
///The number of test cases that were inconclusive when running the test and all its children.
/// </summary>
int InconclusiveCount { get; }
/// <summary>
/// Accessing HasChildren should not force creation of the Children collection in classes implementing this interface.
/// </summary>
/// <value>True if this result has any child results.</value>
bool HasChildren { get; }
/// <summary>
/// Gets the the collection of child results.
/// </summary>
IEnumerable<ITestResultAdaptor> Children { get; }
/// <summary>
/// Gets any text output written to this result.
/// </summary>
string Output { get; }
/// <summary>
/// Use this to save the results to an XML file
/// </summary>
/// <returns>
/// The test results as an `NUnit` XML node.
/// </returns>
TNode ToXml();
}
}

View File

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

View File

@@ -0,0 +1,16 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// ITestRunSettings lets you set any of the global settings right before building a Player for a test run and then reverts the settings afterward. ITestRunSettings implements
/// [IDisposable](https://docs.microsoft.com/en-us/dotnet/api/system.idisposable?view=netframework-4.8), and runs after building the Player with tests.
/// </summary>
public interface ITestRunSettings : IDisposable
{
/// <summary>
/// A method called before building the Player.
/// </summary>
void Apply();
}
}

View File

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

View File

@@ -0,0 +1,12 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal interface ITestRunnerApi
{
string Execute(ExecutionSettings executionSettings);
void RegisterCallbacks<T>(T testCallbacks, int priority = 0) where T : ICallbacks;
void UnregisterCallbacks<T>(T testCallbacks) where T : ICallbacks;
void RetrieveTestList(TestMode testMode, Action<ITestAdaptor> callback);
}
}

View File

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

View File

@@ -0,0 +1,9 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal interface ITestTreeRebuildCallbacks : ICallbacks
{
void TestTreeRebuild(ITestAdaptor test);
}
}

View File

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

View File

@@ -0,0 +1,26 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
[Serializable]
internal class IgnoreTest
{
public string test { get; set; }
public string ignoreComment { get; set; }
public UnityEngine.TestTools.IgnoreTest ParseToEngine()
{
return new UnityEngine.TestTools.IgnoreTest
{
test = test,
ignoreComment = ignoreComment
};
}
public override string ToString()
{
return $"'{test}': '{ignoreComment}'";
}
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: a785dd14005a471483d6732fabeee0ea
timeCreated: 1675763396

View File

@@ -0,0 +1,103 @@
using System;
using System.IO;
using System.Xml;
using NUnit.Framework.Interfaces;
using UnityEditor.TestTools.TestRunner.Api;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal class ResultsWriter
{
private const string k_nUnitVersion = "3.5.0.0";
private const string k_TestRunNode = "test-run";
private const string k_Id = "id";
private const string k_Testcasecount = "testcasecount";
private const string k_Result = "result";
private const string k_Total = "total";
private const string k_Passed = "passed";
private const string k_Failed = "failed";
private const string k_Inconclusive = "inconclusive";
private const string k_Skipped = "skipped";
private const string k_Asserts = "asserts";
private const string k_EngineVersion = "engine-version";
private const string k_ClrVersion = "clr-version";
private const string k_StartTime = "start-time";
private const string k_EndTime = "end-time";
private const string k_Duration = "duration";
private const string k_TimeFormat = "u";
public void WriteResultToFile(ITestResultAdaptor result, string filePath)
{
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Saving results to: {0}", filePath);
try
{
if (!Directory.Exists(filePath))
{
CreateDirectory(filePath);
}
using (var fileStream = File.CreateText(filePath))
{
WriteResultToStream(result, fileStream);
}
}
catch (Exception ex)
{
Debug.LogError("Saving result file failed.");
Debug.LogException(ex);
}
}
private void CreateDirectory(string filePath)
{
var driectoryPath = Path.GetDirectoryName(filePath);
if (!String.IsNullOrEmpty(driectoryPath))
{
Directory.CreateDirectory(driectoryPath);
}
}
public void WriteResultToStream(ITestResultAdaptor result, StreamWriter streamWriter, XmlWriterSettings settings = null)
{
settings = settings ?? new XmlWriterSettings();
settings.Indent = true;
settings.NewLineOnAttributes = false;
using (var xmlWriter = XmlWriter.Create(streamWriter, settings))
{
WriteResultsToXml(result, xmlWriter);
}
}
private void WriteResultsToXml(ITestResultAdaptor result, XmlWriter xmlWriter)
{
// XML format as specified at https://github.com/nunit/docs/wiki/Test-Result-XML-Format
var testRunNode = new TNode(k_TestRunNode);
testRunNode.AddAttribute(k_Id, "2");
testRunNode.AddAttribute(k_Testcasecount, (result.PassCount + result.FailCount + result.SkipCount + result.InconclusiveCount).ToString());
testRunNode.AddAttribute(k_Result, result.ResultState);
testRunNode.AddAttribute(k_Total, (result.PassCount + result.FailCount + result.SkipCount + result.InconclusiveCount).ToString());
testRunNode.AddAttribute(k_Passed, result.PassCount.ToString());
testRunNode.AddAttribute(k_Failed, result.FailCount.ToString());
testRunNode.AddAttribute(k_Inconclusive, result.InconclusiveCount.ToString());
testRunNode.AddAttribute(k_Skipped, result.SkipCount.ToString());
testRunNode.AddAttribute(k_Asserts, result.AssertCount.ToString());
testRunNode.AddAttribute(k_EngineVersion, k_nUnitVersion);
testRunNode.AddAttribute(k_ClrVersion, Environment.Version.ToString());
testRunNode.AddAttribute(k_StartTime, result.StartTime.ToString(k_TimeFormat));
testRunNode.AddAttribute(k_EndTime, result.EndTime.ToString(k_TimeFormat));
testRunNode.AddAttribute(k_Duration, result.Duration.ToString());
var resultNode = result.ToXml();
testRunNode.ChildNodes.Add(resultNode);
testRunNode.WriteTo(xmlWriter);
}
}
}

View File

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

View File

@@ -0,0 +1,35 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// The RunState enum indicates whether a test can be executed.
/// </summary>
public enum RunState
{
/// <summary>
/// The test is not runnable.
/// </summary>
NotRunnable,
/// <summary>
/// The test is runnable.
/// </summary>
Runnable,
/// <summary>
/// The test can only be run explicitly
/// </summary>
Explicit,
/// <summary>
/// The test has been skipped. This value may appear on a Test when certain attributes are used to skip the test.
/// </summary>
Skipped,
/// <summary>
/// The test has been ignored. May appear on a Test, when the IgnoreAttribute is used.
/// </summary>
Ignored
}
}

View File

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

View File

@@ -0,0 +1,143 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework.Interfaces;
using NUnit.Framework.Internal;
using UnityEngine;
using UnityEngine.TestRunner.NUnitExtensions;
using UnityEngine.TestRunner.NUnitExtensions.Runner;
using UnityEngine.TestRunner.TestLaunchers;
using UnityEngine.TestTools;
using UnityEngine.TestTools.Utils;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal class TestAdaptor : ITestAdaptor
{
internal TestAdaptor(ITest test, ITestAdaptor[] children = null)
{
Id = test.Id;
Name = test.Name;
var childIndex = -1;
if (test.Properties["childIndex"].Count > 0)
{
childIndex = (int)test.Properties["childIndex"][0];
}
FullName = TestExtensions.GetFullName(test.FullName, childIndex);
TestCaseCount = test.TestCaseCount;
HasChildren = test.HasChildren;
IsSuite = test.IsSuite;
if (UnityTestExecutionContext.CurrentContext != null)
{
TestCaseTimeout = UnityTestExecutionContext.CurrentContext.TestCaseTimeout;
}
else
{
TestCaseTimeout = UnityWorkItem.k_DefaultTimeout;
}
TypeInfo = test.TypeInfo;
Method = test.Method;
Arguments = test is TestMethod testMethod ? testMethod.parms?.Arguments : (test as TestSuite)?.Arguments;
Categories = test.GetAllCategoriesFromTest().Distinct().ToArray();
IsTestAssembly = test is TestAssembly;
RunState = (RunState)Enum.Parse(typeof(RunState), test.RunState.ToString());
Description = (string)test.Properties.Get(PropertyNames.Description);
SkipReason = test.GetSkipReason();
ParentId = test.GetParentId();
ParentFullName = test.GetParentFullName();
UniqueName = test.GetUniqueName();
ParentUniqueName = test.GetParentUniqueName();
ChildIndex = childIndex;
var testPlatform = test.Properties.Get("platform");
if (testPlatform is TestPlatform platform)
{
TestMode = PlatformToTestMode(platform);
}
Children = children;
}
public void SetParent(ITestAdaptor parent)
{
Parent = parent;
if (parent != null)
{
TestMode = parent.TestMode;
}
}
internal TestAdaptor(RemoteTestData test)
{
Id = test.id;
Name = test.name;
FullName = TestExtensions.GetFullName(test.fullName, test.ChildIndex);
TestCaseCount = test.testCaseCount;
HasChildren = test.hasChildren;
IsSuite = test.isSuite;
m_ChildrenIds = test.childrenIds;
TestCaseTimeout = test.testCaseTimeout;
Categories = test.Categories;
IsTestAssembly = test.IsTestAssembly;
RunState = (RunState)Enum.Parse(typeof(RunState), test.RunState.ToString());
Description = test.Description;
SkipReason = test.SkipReason;
ParentId = test.ParentId;
UniqueName = test.UniqueName;
ParentUniqueName = test.ParentUniqueName;
ParentFullName = test.ParentFullName;
ChildIndex = test.ChildIndex;
TestMode = TestMode.PlayMode;
}
internal void ApplyChildren(IEnumerable<TestAdaptor> allTests)
{
Children = m_ChildrenIds.Select(id => allTests.First(t => t.Id == id)).ToArray();
if (!string.IsNullOrEmpty(ParentId))
{
Parent = allTests.FirstOrDefault(t => t.Id == ParentId);
}
}
private static TestMode PlatformToTestMode(TestPlatform testPlatform)
{
switch (testPlatform)
{
case TestPlatform.All:
return TestMode.EditMode | TestMode.PlayMode;
case TestPlatform.EditMode:
return TestMode.EditMode;
case TestPlatform.PlayMode:
return TestMode.PlayMode;
default:
return default;
}
}
public string Id { get; private set; }
public string Name { get; private set; }
public string FullName { get; private set; }
public int TestCaseCount { get; private set; }
public bool HasChildren { get; private set; }
public bool IsSuite { get; private set; }
public IEnumerable<ITestAdaptor> Children { get; private set; }
public ITestAdaptor Parent { get; private set; }
public int TestCaseTimeout { get; private set; }
public ITypeInfo TypeInfo { get; private set; }
public IMethodInfo Method { get; private set; }
public object[] Arguments { get; }
private string[] m_ChildrenIds;
public string[] Categories { get; private set; }
public bool IsTestAssembly { get; private set; }
public RunState RunState { get; }
public string Description { get; }
public string SkipReason { get; }
public string ParentId { get; }
public string ParentFullName { get; }
public string UniqueName { get; }
public string ParentUniqueName { get; }
public int ChildIndex { get; }
public TestMode TestMode { get; private set; }
}
}

View File

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

View File

@@ -0,0 +1,130 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework.Interfaces;
using UnityEngine.TestRunner.NUnitExtensions;
using UnityEngine.TestRunner.TestLaunchers;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal class TestAdaptorFactory : ITestAdaptorFactory
{
private Dictionary<string, TestAdaptor> m_TestAdaptorCache = new Dictionary<string, TestAdaptor>();
private Dictionary<string, TestResultAdaptor> m_TestResultAdaptorCache = new Dictionary<string, TestResultAdaptor>();
public ITestAdaptor Create(ITest test)
{
var cacheKey = string.Concat(test.GetUniqueName(),test.Properties.Get("platform"));
if (test.Properties.ContainsKey("platform"))
{
cacheKey = string.Concat(cacheKey, test.Properties.Get("platform"));
}
var elementIsModified = test.Properties.ContainsKey(OrderedTestSuiteModifier.suiteIsReorderedProperty);
if (!elementIsModified && m_TestAdaptorCache.ContainsKey(cacheKey))
{
return m_TestAdaptorCache[cacheKey];
}
var adaptor = new TestAdaptor(test, test.Tests.Select(Create).ToArray());
foreach (var child in adaptor.Children)
{
(child as TestAdaptor).SetParent(adaptor);
}
if (!elementIsModified)
{
m_TestAdaptorCache[cacheKey] = adaptor;
}
return adaptor;
}
public ITestAdaptor Create(RemoteTestData testData)
{
return new TestAdaptor(testData);
}
public ITestResultAdaptor Create(ITestResult testResult)
{
var cacheKey = string.Join(";", testResult.Test.GetUniqueName(), testResult.Test.GetRetryIteration(), testResult.Test.GetRepeatIteration());
if (m_TestResultAdaptorCache.ContainsKey(cacheKey))
{
return m_TestResultAdaptorCache[cacheKey];
}
var adaptor = new TestResultAdaptor(testResult, Create(testResult.Test), testResult.Children.Select(Create).ToArray());
m_TestResultAdaptorCache[cacheKey] = adaptor;
return adaptor;
}
public ITestAdaptor Create(ITest test, ITestFilter filter)
{
if (filter == null)
return Create(test);
if (!filter.Pass(test))
{
if (test.Parent == null)
{
// Create an empty root.
return new TestAdaptor(test, children: new ITestAdaptor[0]);
}
return null;
}
var children = test.Tests
.Select(c => Create(c, filter))
.Where(c => c != null)
.ToArray();
var adaptor = new TestAdaptor(test, children: children);
foreach (var child in adaptor.Children)
(child as TestAdaptor).SetParent(adaptor);
return adaptor;
}
public ITestResultAdaptor Create(RemoteTestResultData testResult, RemoteTestResultDataWithTestData allData)
{
return new TestResultAdaptor(testResult, allData);
}
public ITestAdaptor BuildTree(RemoteTestResultDataWithTestData data)
{
var tests = data.tests.Select(remoteTestData => new TestAdaptor(remoteTestData)).ToList();
foreach (var test in tests)
{
test.ApplyChildren(tests);
}
return tests.First();
}
public IEnumerator<ITestAdaptor> BuildTreeAsync(RemoteTestResultDataWithTestData data)
{
var tests = data.tests.Select(remoteTestData => new TestAdaptor(remoteTestData)).ToList();
for (var index = 0; index < tests.Count; index++)
{
var test = tests[index];
test.ApplyChildren(tests);
if (index % 100 == 0)
{
yield return null;
}
}
yield return tests.First();
}
public void ClearResultsCache()
{
m_TestResultAdaptorCache.Clear();
}
public void ClearTestsCache()
{
m_TestAdaptorCache.Clear();
}
}
}

View File

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

View File

@@ -0,0 +1,20 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// A flag indicating whether to run Edit Mode or Play Mode tests.
/// </summary>
[Flags]
public enum TestMode
{
/// <summary>
/// Run EditMode tests.
/// </summary>
EditMode = 1 << 0,
/// <summary>
/// Run PlayMode tests.
/// </summary>
PlayMode = 1 << 1
}
}

View File

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

View File

@@ -0,0 +1,104 @@
using System;
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework.Interfaces;
using UnityEngine.TestRunner.NUnitExtensions;
using UnityEngine.TestRunner.TestLaunchers;
namespace UnityEditor.TestTools.TestRunner.Api
{
internal class TestResultAdaptor : ITestResultAdaptor
{
private TNode m_Node;
private ITestResult m_Result;
internal TestResultAdaptor(ITestResult result, ITestAdaptor test, ITestResultAdaptor[] children = null)
{
Test = test;
Name = result.Name;
FullName = result.FullName;
ResultState = result.ResultState.ToString();
TestStatus = ParseTestStatus(result.ResultState.Status);
Duration = result.Duration;
StartTime = result.StartTime;
EndTime = result.EndTime;
Message = result.Message;
StackTrace = result.StackTrace;
AssertCount = result.AssertCount;
FailCount = result.FailCount;
PassCount = result.PassCount;
SkipCount = result.SkipCount;
InconclusiveCount = result.InconclusiveCount;
HasChildren = result.HasChildren;
Output = result.Output;
Children = children;
m_Result = result;
RetryIteration = result.Test.GetRetryIteration();
RepeatIteration = result.Test.GetRepeatIteration();
}
internal TestResultAdaptor(RemoteTestResultData result, RemoteTestResultDataWithTestData allData)
{
Test = new TestAdaptor(allData.tests.First(t => t.id == result.testId));
Name = result.name;
FullName = result.fullName;
ResultState = result.resultState;
TestStatus = ParseTestStatus(result.testStatus);
Duration = result.duration;
StartTime = result.startTime;
EndTime = result.endTime;
Message = result.message;
StackTrace = result.stackTrace;
AssertCount = result.assertCount;
FailCount = result.failCount;
PassCount = result.passCount;
SkipCount = result.skipCount;
InconclusiveCount = result.inconclusiveCount;
HasChildren = result.hasChildren;
Output = result.output;
Children = result.childrenIds.Select(childId =>
new TestResultAdaptor(allData.results.First(r => r.testId == childId), allData)).ToArray();
if (!string.IsNullOrEmpty(result.xml))
{
m_Node = TNode.FromXml(result.xml);
}
}
public ITestAdaptor Test { get; private set; }
public string Name { get; private set; }
public string FullName { get; private set; }
public string ResultState { get; private set; }
public TestStatus TestStatus { get; private set; }
public double Duration { get; private set; }
public DateTime StartTime { get; private set; }
public DateTime EndTime { get; private set; }
public string Message { get; private set; }
public string StackTrace { get; private set; }
public int AssertCount { get; private set; }
public int FailCount { get; private set; }
public int PassCount { get; private set; }
public int SkipCount { get; private set; }
public int InconclusiveCount { get; private set; }
public bool HasChildren { get; private set; }
public IEnumerable<ITestResultAdaptor> Children { get; private set; }
public string Output { get; private set; }
public TNode ToXml()
{
if (m_Node == null)
{
m_Node = m_Result.ToXml(true);
}
return m_Node;
}
internal int RetryIteration { get; set; }
internal int RepeatIteration { get; set; }
private static TestStatus ParseTestStatus(NUnit.Framework.Interfaces.TestStatus testStatus)
{
return (TestStatus)Enum.Parse(typeof(TestStatus), testStatus.ToString());
}
}
}

View File

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

View File

@@ -0,0 +1,24 @@
using System;
using UnityEngine;
namespace UnityEditor.TestTools.TestRunner.Api
{
[Serializable]
internal class TestRunProgress
{
[SerializeField]
public string RunGuid;
[SerializeField]
public ExecutionSettings ExecutionSettings;
[SerializeField]
public bool HasFinished;
[SerializeField]
public float Progress;
[SerializeField]
public string CurrentStepName;
[SerializeField]
public string CurrentStageName;
}
}

View File

@@ -0,0 +1,3 @@
fileFormatVersion: 2
guid: 8a06b6fbaa6f4191aa47b7e31242f061
timeCreated: 1659007168

View File

@@ -0,0 +1,231 @@
using System;
using System.Linq;
using UnityEditor.TestTools.TestRunner.CommandLineTest;
using UnityEditor.TestTools.TestRunner.TestRun;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.TestRunner.NUnitExtensions.Runner;
using UnityEngine.TestRunner.TestLaunchers;
using UnityEngine.TestTools;
using UnityEngine.TestTools.NUnitExtensions;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// <para>The TestRunnerApi retrieves and runs tests programmatically from code inside the project, or inside other packages. TestRunnerApi is a [ScriptableObject](https://docs.unity3d.com/ScriptReference/ScriptableObject.html).
/// You can initialize the API like this:</para>
/// <code>
/// var testRunnerApi = ScriptableObject.CreateInstance&lt;TestRunnerApi&gt;();
/// </code>
/// <para>
/// Note: You can subscribe and receive test results in one instance of the API, even if the run starts from another instance.
/// The TestRunnerApi supports the following workflows:
/// - [How to run tests programmatically](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/extension-run-tests.html)
/// - [How to get test results](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/extension-get-test-results.html)
/// - [How to retrieve the list of tests](https://docs.unity3d.com/Packages/com.unity.test-framework@1.1/manual/extension-retrieve-test-list.html)
/// </para>
/// </summary>
public class TestRunnerApi : ScriptableObject, ITestRunnerApi
{
internal static ICallbacksHolder callbacksHolder;
private static ICallbacksHolder CallbacksHolder
{
get
{
if (callbacksHolder == null)
{
callbacksHolder = Api.CallbacksHolder.instance;
}
return callbacksHolder;
}
}
internal static ITestJobDataHolder testJobDataHolder;
private static ITestJobDataHolder m_testJobDataHolder
{
get { return testJobDataHolder ?? (testJobDataHolder = TestJobDataHolder.instance); }
}
internal Func<ExecutionSettings,string> ScheduleJob = executionSettings =>
{
var runner = new TestJobRunner();
return runner.RunJob(new TestJobData(executionSettings));
};
/// <summary>
/// Starts a test run with a given set of executionSettings.
/// </summary>
/// <param name="executionSettings">Set of <see cref="ExecutionSettings"/></param>
/// <returns>A GUID that identifies the TestJobData.</returns>
public string Execute(ExecutionSettings executionSettings)
{
if (executionSettings == null)
{
throw new ArgumentNullException(nameof(executionSettings));
}
if ((executionSettings.filters == null || executionSettings.filters.Length == 0) && executionSettings.filter != null)
{
// Map filter (singular) to filters (plural), for backwards compatibility.
executionSettings.filters = new[] { executionSettings.filter };
}
if (executionSettings.targetPlatform == null && executionSettings.filters != null &&
executionSettings.filters.Length > 0)
{
executionSettings.targetPlatform = executionSettings.filters[0].targetPlatform;
}
if (executionSettings.featureFlags == null)
{
executionSettings.featureFlags = new FeatureFlags();
}
return ScheduleJob(executionSettings);
}
/// <summary>
/// Sets up a given instance of <see cref="ICallbacks"/> to be invoked on test runs.
/// </summary>
/// <typeparam name="T">
/// Generic representing a type of callback.
/// </typeparam>
/// <param name="testCallbacks">
/// The test callbacks to be invoked.
/// </param>
/// <param name="priority">
/// Sets the order in which the callbacks are invoked, starting with the highest value first.
/// </param>
public void RegisterCallbacks<T>(T testCallbacks, int priority = 0) where T : ICallbacks
{
RegisterTestCallback(testCallbacks, priority);
}
/// <summary>
/// Sets up a given instance of <see cref="ICallbacks"/> to be invoked on test runs.
/// </summary>
/// <typeparam name="T">
/// Generic representing a type of callback.
/// </typeparam>
/// <param name="testCallbacks">The test callbacks to be invoked</param>
/// <param name="priority">
/// Sets the order in which the callbacks are invoked, starting with the highest value first.
/// </param>
public static void RegisterTestCallback<T>(T testCallbacks, int priority = 0) where T : ICallbacks
{
if (testCallbacks == null)
{
throw new ArgumentNullException(nameof(testCallbacks));
}
CallbacksHolder.Add(testCallbacks, priority);
}
/// <summary>
/// Unregister an instance of <see cref="ICallbacks"/> to no longer receive callbacks from test runs.
/// </summary>
/// <typeparam name="T">
/// Generic representing a type of callback.
/// </typeparam>
/// <param name="testCallbacks">The test callbacks to unregister.</param>
public void UnregisterCallbacks<T>(T testCallbacks) where T : ICallbacks
{
UnregisterTestCallback(testCallbacks);
}
/// <summary>
/// Unregister an instance of <see cref="ICallbacks"/> to no longer receive callbacks from test runs.
/// </summary>
/// <typeparam name="T">
/// Generic representing a type of callback.
/// </typeparam>
/// <param name="testCallbacks">The test callbacks to unregister.</param>
public static void UnregisterTestCallback<T>(T testCallbacks) where T : ICallbacks
{
if (testCallbacks == null)
{
throw new ArgumentNullException(nameof(testCallbacks));
}
CallbacksHolder.Remove(testCallbacks);
}
internal void RetrieveTestList(ExecutionSettings executionSettings, Action<ITestAdaptor> callback)
{
if (executionSettings == null)
{
throw new ArgumentNullException(nameof(executionSettings));
}
var firstFilter = executionSettings.filters?.FirstOrDefault() ?? executionSettings.filter;
RetrieveTestList(firstFilter.testMode, callback);
}
/// <summary>
/// Retrieve the full test tree as ITestAdaptor for a given test mode. This is obsolete. Use TestRunnerApi.RetrieveTestTree instead.
/// </summary>
/// <param name="testMode">The TestMode to retrieve the test list for.</param>
/// <param name="callback">A callback that is invoked when the test tree is retrieved.</param>
public void RetrieveTestList(TestMode testMode, Action<ITestAdaptor> callback)
{
if (callback == null)
{
throw new ArgumentNullException(nameof(callback));
}
var platform = ParseTestMode(testMode);
var testAssemblyProvider = new EditorLoadedTestAssemblyProvider(new EditorCompilationInterfaceProxy(), new EditorAssembliesProxy());
var testAdaptorFactory = new TestAdaptorFactory();
var testListCache = new TestListCache(testAdaptorFactory, new RemoteTestResultDataFactory(), TestListCacheData.instance);
var testListProvider = new TestListProvider(testAssemblyProvider, new UnityTestAssemblyBuilder(null, 0));
var cachedTestListProvider = new CachingTestListProvider(testListProvider, testListCache, testAdaptorFactory);
var job = new TestListJob(cachedTestListProvider, platform, testRoot =>
{
callback(testRoot);
});
job.Start();
}
///<summary>
/// Save a given set of ITestResultAdaptor in [NUnit XML Format](https://docs.nunit.org/articles/nunit/technical-notes/usage/Test-Result-XML-Format.html) to a file at the provided file path. Any matching existing file is overwritten.
/// </summary>
/// <param name="results">Test results to write in a file.</param>
/// <param name="xmlFilePath">An xml file path relative to the project folder.</param>
public static void SaveResultToFile(ITestResultAdaptor results, string xmlFilePath)
{
var resultsWriter = new ResultsWriter();
resultsWriter.WriteResultToFile(results, xmlFilePath);
}
/// <summary>
/// Cancel the test run with a given guid string. The guid string can be retrieved when executing the test run. The test run may take multiple frames to finish cleaning up from the test run. Any current active test will be marked as "Canceled" and any other remaining tests marked as "NotRun".
/// </summary>
/// <param name="guid">Test run guid string.</param>
/// <returns>A boolean indicating whether canceling of the given job was successful. Canceling of a job will not be a success if no test job is found matching the guid, if the job is not currently or the job is already canceling.</returns>
public static bool CancelTestRun(string guid)
{
var runner = m_testJobDataHolder.GetRunner(guid);
if (runner == null || !runner.IsRunningJob())
{
return false;
}
return runner.CancelRun();
}
internal static bool IsRunActive()
{
return m_testJobDataHolder.GetAllRunners().Any(r => r.GetData().isRunning);
}
private static TestPlatform ParseTestMode(TestMode testMode)
{
return (((testMode & TestMode.EditMode) == TestMode.EditMode) ? TestPlatform.EditMode : 0) | (((testMode & TestMode.PlayMode) == TestMode.PlayMode) ? TestPlatform.PlayMode : 0);
}
internal class RunProgressChangedEvent : UnityEvent<TestRunProgress> {}
internal static RunProgressChangedEvent runProgressChanged = new RunProgressChangedEvent();
}
}

View File

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

View File

@@ -0,0 +1,30 @@
using System;
namespace UnityEditor.TestTools.TestRunner.Api
{
/// <summary>
/// The TestStatus enum indicates the test result status.
/// </summary>
public enum TestStatus
{
/// <summary>
/// The test ran with an inconclusive result.
/// </summary>
Inconclusive,
/// <summary>
/// The test was skipped.
/// </summary>
Skipped,
/// <summary>
/// The test ran and passed.
/// </summary>
Passed,
/// <summary>
/// The test ran and failed.
/// </summary>
Failed
}
}

View File

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

View File

@@ -0,0 +1,15 @@
using System;
using System.Reflection;
using System.Runtime.CompilerServices;
[assembly: AssemblyTitle("UnityEditor.TestRunner")]
[assembly: InternalsVisibleTo("Assembly-CSharp-Editor-testable")]
[assembly: InternalsVisibleTo("DynamicProxyGenAssembly2")]
[assembly: InternalsVisibleTo("Unity.PerformanceTesting.Editor")]
[assembly: InternalsVisibleTo("Unity.IntegrationTests")]
[assembly: InternalsVisibleTo("UnityEditor.TestRunner.Tests")]
[assembly: InternalsVisibleTo("Unity.PackageManagerUI.Develop.Editor")]
[assembly: InternalsVisibleTo("Unity.PackageManagerUI.Develop.EditorTests")]
[assembly: InternalsVisibleTo("Unity.PackageValidationSuite.Editor")]
[assembly: AssemblyVersion("1.0.0")]

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7602252bdb82b8d45ae3483c3a00d3e1
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,44 @@
using System;
namespace UnityEditor.TestRunner.CommandLineParser
{
internal class CommandLineOption : ICommandLineOption
{
private Action<string> m_ArgAction;
public CommandLineOption(string argName, Action action)
{
ArgName = argName;
m_ArgAction = s => action();
}
public CommandLineOption(string argName, Action<string> action)
{
ArgName = argName;
m_ArgAction = action;
}
public CommandLineOption(string argName, Action<string[]> action)
{
ArgName = argName;
m_ArgAction = s => action(SplitStringToArray(s));
}
public string ArgName { get; private set; }
public void ApplyValue(string value)
{
m_ArgAction(value);
}
private static string[] SplitStringToArray(string value)
{
if (string.IsNullOrEmpty(value))
{
return null;
}
return value.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);
}
}
}

View File

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

View File

@@ -0,0 +1,49 @@
using System;
namespace UnityEditor.TestRunner.CommandLineParser
{
internal class CommandLineOptionSet
{
private ICommandLineOption[] m_Options;
public CommandLineOptionSet(params ICommandLineOption[] options)
{
m_Options = options;
}
public void Parse(string[] args)
{
var i = 0;
while (i < args.Length)
{
var arg = args[i];
if (!arg.StartsWith("-"))
{
i++;
continue;
}
string value = null;
if (i + 1 < args.Length && !args[i + 1].StartsWith("-"))
{
value = args[i + 1];
i++;
}
ApplyValueToMatchingOptions(arg, value);
i++;
}
}
private void ApplyValueToMatchingOptions(string argName, string value)
{
foreach (var option in m_Options)
{
if ("-" + option.ArgName == argName)
{
option.ApplyValue(value);
}
}
}
}
}

View File

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

View File

@@ -0,0 +1,10 @@
using System;
namespace UnityEditor.TestRunner.CommandLineParser
{
internal interface ICommandLineOption
{
string ArgName { get; }
void ApplyValue(string value);
}
}

View File

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b477d1f29b65a674e9d5cdab4eb72b01
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

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

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 7e609b27ad2caa14c83dd9951b6c13c6
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,13 @@
using System;
namespace UnityEditor.TestTools.TestRunner.GUI
{
internal class AssetsDatabaseHelper : IAssetsDatabaseHelper
{
public void OpenAssetInItsDefaultExternalEditor(string assetPath, int line)
{
var asset = AssetDatabase.LoadMainAssetAtPath(assetPath);
AssetDatabase.OpenAsset(asset, line);
}
}
}

Some files were not shown because too many files have changed in this diff Show More