first commit

This commit is contained in:
lethanhsonvsp
2025-11-17 15:16:36 +07:00
commit a40d0921eb
17012 changed files with 2652386 additions and 0 deletions

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: