testss
This commit is contained in:
@@ -0,0 +1,136 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal;
|
||||
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;
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
m_AdaptorFactory.ClearTestsCache();
|
||||
var testAdaptor = m_AdaptorFactory.Create(test);
|
||||
TryInvokeAllCallbacks(callbacks =>
|
||||
{
|
||||
var rebuildCallbacks = callbacks as ITestTreeRebuildCallbacks;
|
||||
if (rebuildCallbacks != null)
|
||||
{
|
||||
rebuildCallbacks.TestTreeRebuild(testAdaptor);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.Api
|
||||
{
|
||||
internal class CallbacksDelegatorListener : ScriptableObject, ITestRunnerListener
|
||||
{
|
||||
public void RunStarted(NUnit.Framework.Interfaces.ITest testsToRun)
|
||||
{
|
||||
CallbacksDelegator.instance.RunStarted(testsToRun);
|
||||
}
|
||||
|
||||
public void RunFinished(NUnit.Framework.Interfaces.ITestResult testResults)
|
||||
{
|
||||
CallbacksDelegator.instance.RunFinished(testResults);
|
||||
}
|
||||
|
||||
public void TestStarted(NUnit.Framework.Interfaces.ITest test)
|
||||
{
|
||||
CallbacksDelegator.instance.TestStarted(test);
|
||||
}
|
||||
|
||||
public void TestFinished(NUnit.Framework.Interfaces.ITestResult result)
|
||||
{
|
||||
CallbacksDelegator.instance.TestFinished(result);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,77 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal.Filters;
|
||||
using UnityEngine;
|
||||
|
||||
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]
|
||||
internal BuildTarget? targetPlatform;
|
||||
|
||||
/// <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>
|
||||
/// <returns>If true, the call to Execute() will run tests synchronously, guaranteeing that all tests have finished running by the time the call returns.</returns>
|
||||
[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;
|
||||
|
||||
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());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,56 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
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.
|
||||
/// </summary>
|
||||
[SerializeField]
|
||||
public BuildTarget? targetPlatform;
|
||||
|
||||
internal RuntimeTestRunnerFilter ToRuntimeTestRunnerFilter(bool synchronousOnly)
|
||||
{
|
||||
return new RuntimeTestRunnerFilter()
|
||||
{
|
||||
testNames = testNames,
|
||||
categoryNames = categoryNames,
|
||||
groupNames = groupNames,
|
||||
assemblyNames = assemblyNames,
|
||||
synchronousOnly = synchronousOnly
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
namespace UnityEditor.TestTools.TestRunner.Api
|
||||
{
|
||||
internal interface ICallbacksHolder
|
||||
{
|
||||
void Add(ICallbacks callback, int priority);
|
||||
void Remove(ICallbacks callback);
|
||||
ICallbacks[] GetAll();
|
||||
void Clear();
|
||||
}
|
||||
}
|
@@ -0,0 +1,16 @@
|
||||
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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,100 @@
|
||||
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>
|
||||
/// 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; }
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
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);
|
||||
ITestResultAdaptor Create(RemoteTestResultData testResult, RemoteTestResultDataWithTestData allData);
|
||||
ITestAdaptor BuildTree(RemoteTestResultDataWithTestData data);
|
||||
IEnumerator<ITestAdaptor> BuildTreeAsync(RemoteTestResultDataWithTestData data);
|
||||
void ClearResultsCache();
|
||||
void ClearTestsCache();
|
||||
}
|
||||
}
|
@@ -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>
|
||||
///<returns>
|
||||
///The name of the test result.
|
||||
///</returns>
|
||||
string FullName { get; }
|
||||
///<summary>
|
||||
///Gets the state of the result as a string.
|
||||
///</summary>
|
||||
///<returns>
|
||||
///It returns one of these values: `Inconclusive`, `Skipped`, `Skipped:Ignored`, `Skipped:Explicit`, `Passed`, `Failed`, `Failed:Error`, `Failed:Cancelled`, `Failed:Invalid`
|
||||
///</returns>
|
||||
string ResultState { get; }
|
||||
///<summary>
|
||||
///Gets the status of the test as an enum.
|
||||
///</summary>
|
||||
///<returns>
|
||||
///It returns one of these values:`Inconclusive`, `Skipped`, `Passed`, or `Failed`
|
||||
///</returns>
|
||||
TestStatus TestStatus { get; }
|
||||
/// <summary>
|
||||
/// Gets the elapsed time for running the test in seconds
|
||||
/// </summary>
|
||||
/// <returns>
|
||||
/// Time in seconds.
|
||||
/// </returns>
|
||||
double Duration { get; }
|
||||
/// <summary>
|
||||
/// Gets or sets the time the test started running.
|
||||
/// </summary>
|
||||
///<returns>
|
||||
///A DataTime object.
|
||||
///</returns>
|
||||
DateTime StartTime { get; }
|
||||
///<summary>
|
||||
///Gets or sets the time the test finished running.
|
||||
///</summary>
|
||||
///<returns>
|
||||
///A DataTime object.
|
||||
///</returns>
|
||||
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>
|
||||
/// <returns>True if this result has any child results.</returns>
|
||||
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();
|
||||
}
|
||||
}
|
@@ -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();
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace UnityEditor.TestTools.TestRunner.Api
|
||||
{
|
||||
internal interface ITestTreeRebuildCallbacks : ICallbacks
|
||||
{
|
||||
void TestTreeRebuild(ITestAdaptor test);
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
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,
|
||||
}
|
||||
}
|
@@ -0,0 +1,142 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal;
|
||||
using UnityEngine.TestRunner.NUnitExtensions;
|
||||
using UnityEngine.TestRunner.NUnitExtensions.Runner;
|
||||
using UnityEngine.TestRunner.TestLaunchers;
|
||||
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 = childIndex != -1 ? GetIndexedTestCaseName(test.FullName, childIndex) : test.FullName;
|
||||
TestCaseCount = test.TestCaseCount;
|
||||
HasChildren = test.HasChildren;
|
||||
IsSuite = test.IsSuite;
|
||||
if (UnityTestExecutionContext.CurrentContext != null)
|
||||
{
|
||||
TestCaseTimeout = UnityTestExecutionContext.CurrentContext.TestCaseTimeout;
|
||||
}
|
||||
else
|
||||
{
|
||||
TestCaseTimeout = CoroutineRunner.k_DefaultTimeout;
|
||||
}
|
||||
|
||||
TypeInfo = test.TypeInfo;
|
||||
Method = test.Method;
|
||||
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;
|
||||
|
||||
if (test.Parent != null)
|
||||
{
|
||||
if (test.Parent.Parent == null) // Assembly level
|
||||
{
|
||||
TestMode = (TestMode)Enum.Parse(typeof(TestMode),test.Properties.Get("platform").ToString());
|
||||
}
|
||||
}
|
||||
|
||||
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 = test.ChildIndex != -1 ? GetIndexedTestCaseName(test.fullName, test.ChildIndex) : test.fullName;
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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; }
|
||||
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; }
|
||||
|
||||
private static string GetIndexedTestCaseName(string fullName, int index)
|
||||
{
|
||||
var generatedTestSuffix = " GeneratedTestCase" + index;
|
||||
if (fullName.EndsWith(")"))
|
||||
{
|
||||
// Test names from generated TestCaseSource look like Test(TestCaseSourceType)
|
||||
// This inserts a unique test case index in the name, so that it becomes Test(TestCaseSourceType GeneratedTestCase0)
|
||||
return fullName.Substring(0, fullName.Length - 1) + generatedTestSuffix + fullName[fullName.Length - 1];
|
||||
}
|
||||
|
||||
// In some cases there can be tests with duplicate names generated in other ways and they won't have () in their name
|
||||
// We just append a suffix at the end of the name in that case
|
||||
return fullName + generatedTestSuffix;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,91 @@
|
||||
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 uniqueName = test.GetUniqueName();
|
||||
if (m_TestAdaptorCache.ContainsKey(uniqueName))
|
||||
{
|
||||
return m_TestAdaptorCache[uniqueName];
|
||||
}
|
||||
|
||||
var adaptor = new TestAdaptor(test, test.Tests.Select(Create).ToArray());
|
||||
foreach (var child in adaptor.Children)
|
||||
{
|
||||
(child as TestAdaptor).SetParent(adaptor);
|
||||
}
|
||||
m_TestAdaptorCache[uniqueName] = adaptor;
|
||||
return adaptor;
|
||||
}
|
||||
|
||||
public ITestAdaptor Create(RemoteTestData testData)
|
||||
{
|
||||
return new TestAdaptor(testData);
|
||||
}
|
||||
|
||||
public ITestResultAdaptor Create(ITestResult testResult)
|
||||
{
|
||||
var uniqueName = testResult.Test.GetUniqueName();
|
||||
if (m_TestResultAdaptorCache.ContainsKey(uniqueName))
|
||||
{
|
||||
return m_TestResultAdaptorCache[uniqueName];
|
||||
}
|
||||
var adaptor = new TestResultAdaptor(testResult, Create(testResult.Test), testResult.Children.Select(Create).ToArray());
|
||||
m_TestResultAdaptorCache[uniqueName] = 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();
|
||||
}
|
||||
}
|
||||
}
|
@@ -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
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
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;
|
||||
}
|
||||
|
||||
private static TestStatus ParseTestStatus(NUnit.Framework.Interfaces.TestStatus testStatus)
|
||||
{
|
||||
return (TestStatus)Enum.Parse(typeof(TestStatus), testStatus.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,158 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using UnityEditor.TestTools.TestRunner.CommandLineTest;
|
||||
using UnityEditor.TestTools.TestRunner.TestRun;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestRunner.TestLaunchers;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.TestTools.NUnitExtensions;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.Api
|
||||
{
|
||||
/// <summary>
|
||||
/// 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:
|
||||
/// ```
|
||||
/// var testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
/// ```
|
||||
/// 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)
|
||||
/// </summary>
|
||||
public class TestRunnerApi : ScriptableObject, ITestRunnerApi
|
||||
{
|
||||
internal ICallbacksHolder callbacksHolder;
|
||||
|
||||
private ICallbacksHolder m_CallbacksHolder
|
||||
{
|
||||
get
|
||||
{
|
||||
if (callbacksHolder == null)
|
||||
{
|
||||
return CallbacksHolder.instance;
|
||||
}
|
||||
|
||||
return callbacksHolder;
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
if (testCallbacks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(testCallbacks));
|
||||
}
|
||||
|
||||
m_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
|
||||
{
|
||||
if (testCallbacks == null)
|
||||
{
|
||||
throw new ArgumentNullException(nameof(testCallbacks));
|
||||
}
|
||||
|
||||
m_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"></param>
|
||||
/// <param name="callback"></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());
|
||||
var cachedTestListProvider = new CachingTestListProvider(testListProvider, testListCache, testAdaptorFactory);
|
||||
|
||||
var job = new TestListJob(cachedTestListProvider, platform, (testRoot) =>
|
||||
{
|
||||
callback(testRoot);
|
||||
});
|
||||
job.Start();
|
||||
}
|
||||
|
||||
internal static bool IsRunActive()
|
||||
{
|
||||
return RunData.instance.isRunning;
|
||||
}
|
||||
|
||||
private static TestPlatform ParseTestMode(TestMode testMode)
|
||||
{
|
||||
return (((testMode & TestMode.EditMode) == TestMode.EditMode) ? TestPlatform.EditMode : 0) | (((testMode & TestMode.PlayMode) == TestMode.PlayMode) ? TestPlatform.PlayMode : 0);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,28 @@
|
||||
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
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
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")]
|
@@ -0,0 +1,45 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
|
||||
namespace UnityEditor.TestRunner.CommandLineParser
|
||||
{
|
||||
internal class CommandLineOption : ICommandLineOption
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
||||
static string[] SplitStringToArray(string value)
|
||||
{
|
||||
if (string.IsNullOrEmpty(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
|
||||
return value.Split(new[] {';'}, StringSplitOptions.RemoveEmptyEntries);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,49 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestRunner.CommandLineParser
|
||||
{
|
||||
internal class CommandLineOptionSet
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace UnityEditor.TestRunner.CommandLineParser
|
||||
{
|
||||
interface ICommandLineOption
|
||||
{
|
||||
string ArgName { get; }
|
||||
void ApplyValue(string value);
|
||||
}
|
||||
}
|
@@ -0,0 +1,134 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.TestRunner.TestLaunchers;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
internal class Executer
|
||||
{
|
||||
private ITestRunnerApi m_TestRunnerApi;
|
||||
private ISettingsBuilder m_SettingsBuilder;
|
||||
private Action<string, object[]> m_LogErrorFormat;
|
||||
private Action<Exception> m_LogException;
|
||||
private Action<int> m_ExitEditorApplication;
|
||||
private Func<bool> m_ScriptCompilationFailedCheck;
|
||||
|
||||
public Executer(ITestRunnerApi testRunnerApi, ISettingsBuilder settingsBuilder, Action<string, object[]> logErrorFormat, Action<Exception> logException, Action<int> exitEditorApplication, Func<bool> scriptCompilationFailedCheck)
|
||||
{
|
||||
m_TestRunnerApi = testRunnerApi;
|
||||
m_SettingsBuilder = settingsBuilder;
|
||||
m_LogErrorFormat = logErrorFormat;
|
||||
m_LogException = logException;
|
||||
m_ExitEditorApplication = exitEditorApplication;
|
||||
m_ScriptCompilationFailedCheck = scriptCompilationFailedCheck;
|
||||
}
|
||||
|
||||
internal void 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;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
Debug.Log("Executing tests with settings: " + ExecutionSettingsToString(executionSettings));
|
||||
m_TestRunnerApi.Execute(executionSettings);
|
||||
}
|
||||
catch (Exception exception)
|
||||
{
|
||||
m_LogException(exception);
|
||||
m_ExitEditorApplication((int)ReturnCodes.RunError);
|
||||
}
|
||||
}
|
||||
|
||||
internal ExecutionSettings BuildExecutionSettings(string[] commandLineArgs)
|
||||
{
|
||||
return m_SettingsBuilder.BuildExecutionSettings(commandLineArgs);
|
||||
}
|
||||
|
||||
internal enum ReturnCodes
|
||||
{
|
||||
Ok = 0,
|
||||
Failed = 2,
|
||||
RunError = 3,
|
||||
PlatformNotFoundReturnCode = 4
|
||||
}
|
||||
|
||||
internal void SetUpCallbacks(ExecutionSettings executionSettings)
|
||||
{
|
||||
RemotePlayerLogController.instance.SetLogsDirectory(executionSettings.DeviceLogsDirectory);
|
||||
|
||||
var resultSavingCallback = ScriptableObject.CreateInstance<ResultsSavingCallbacks>();
|
||||
resultSavingCallback.m_ResultFilePath = executionSettings.TestResultsFile;
|
||||
|
||||
var logSavingCallback = ScriptableObject.CreateInstance<LogSavingCallbacks>();
|
||||
|
||||
m_TestRunnerApi.RegisterCallbacks(resultSavingCallback);
|
||||
m_TestRunnerApi.RegisterCallbacks(logSavingCallback);
|
||||
m_TestRunnerApi.RegisterCallbacks(ScriptableObject.CreateInstance<ExitCallbacks>(), -10);
|
||||
}
|
||||
|
||||
internal 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]);
|
||||
m_ExitEditorApplication(handling.m_ReturnCode);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
m_ExitEditorApplication(handling.m_ReturnCode);
|
||||
}
|
||||
|
||||
private class ExceptionHandling
|
||||
{
|
||||
internal SetupException.ExceptionType m_ExceptionType;
|
||||
internal string m_Message;
|
||||
internal int m_ReturnCode;
|
||||
public ExceptionHandling(SetupException.ExceptionType exceptionType, string message, ReturnCodes returnCode)
|
||||
{
|
||||
m_ExceptionType = exceptionType;
|
||||
m_Message = message;
|
||||
m_ReturnCode = (int)returnCode;
|
||||
}
|
||||
}
|
||||
|
||||
static ExceptionHandling[] s_ExceptionHandlingMapping = new[]
|
||||
{
|
||||
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)
|
||||
};
|
||||
|
||||
private static string ExecutionSettingsToString(Api.ExecutionSettings executionSettings)
|
||||
{
|
||||
if (executionSettings == null)
|
||||
{
|
||||
return "none";
|
||||
}
|
||||
|
||||
if (executionSettings.filters == null || executionSettings.filters.Length == 0)
|
||||
{
|
||||
return "no filter";
|
||||
}
|
||||
|
||||
return "test mode = " + executionSettings.filters[0].testMode;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
[Serializable]
|
||||
internal class ExecutionSettings
|
||||
{
|
||||
public string TestResultsFile;
|
||||
public string DeviceLogsDirectory;
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
[Serializable]
|
||||
internal class ExitCallbacks : ScriptableObject, IErrorCallbacks
|
||||
{
|
||||
internal static bool preventExit;
|
||||
|
||||
public void RunFinished(ITestResultAdaptor testResults)
|
||||
{
|
||||
if (preventExit)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ExitCallbacksDataHolder.instance.AnyTestsExecuted)
|
||||
{
|
||||
Debug.LogFormat(LogType.Warning, LogOption.NoStacktrace, null, "No tests were executed");
|
||||
}
|
||||
|
||||
EditorApplication.Exit(ExitCallbacksDataHolder.instance.RunFailed ? (int)Executer.ReturnCodes.Failed : (int)Executer.ReturnCodes.Ok);
|
||||
}
|
||||
|
||||
public void TestStarted(ITestAdaptor test)
|
||||
{
|
||||
if (!test.IsSuite)
|
||||
{
|
||||
ExitCallbacksDataHolder.instance.AnyTestsExecuted = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void TestFinished(ITestResultAdaptor result)
|
||||
{
|
||||
if (!result.Test.IsSuite && (result.TestStatus == TestStatus.Failed || result.TestStatus == TestStatus.Inconclusive))
|
||||
{
|
||||
ExitCallbacksDataHolder.instance.RunFailed = true;
|
||||
}
|
||||
}
|
||||
|
||||
public void RunStarted(ITestAdaptor testsToRun)
|
||||
{
|
||||
}
|
||||
|
||||
public void OnError(string message)
|
||||
{
|
||||
EditorApplication.Exit((int)Executer.ReturnCodes.RunError);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
internal class ExitCallbacksDataHolder : ScriptableSingleton<ExitCallbacksDataHolder>
|
||||
{
|
||||
[SerializeField]
|
||||
public bool AnyTestsExecuted;
|
||||
[SerializeField]
|
||||
public bool RunFailed;
|
||||
}
|
||||
}
|
@@ -0,0 +1,10 @@
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
interface ISettingsBuilder
|
||||
{
|
||||
Api.ExecutionSettings BuildApiExecutionSettings(string[] commandLineArgs);
|
||||
ExecutionSettings BuildExecutionSettings(string[] commandLineArgs);
|
||||
}
|
||||
}
|
@@ -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)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
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()
|
||||
{
|
||||
this.m_ResultFilePath = GetDefaultResultFilePath();
|
||||
}
|
||||
|
||||
public void RunStarted(ITestAdaptor testsToRun)
|
||||
{
|
||||
}
|
||||
|
||||
public virtual void RunFinished(ITestResultAdaptor testResults)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_ResultFilePath))
|
||||
{
|
||||
m_ResultFilePath = GetDefaultResultFilePath();
|
||||
}
|
||||
|
||||
var resultWriter = new ResultsWriter();
|
||||
resultWriter.WriteResultToFile(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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -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.CommandLineTest
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
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.ToString());
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,8 @@
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
internal class RunData : ScriptableSingleton<RunData>
|
||||
{
|
||||
public bool isRunning;
|
||||
public ExecutionSettings executionSettings;
|
||||
}
|
||||
}
|
@@ -0,0 +1,29 @@
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
internal class RunSettings : ITestRunSettings
|
||||
{
|
||||
private ITestSettings m_TestSettings;
|
||||
public RunSettings(ITestSettings testSettings)
|
||||
{
|
||||
this.m_TestSettings = testSettings;
|
||||
}
|
||||
|
||||
public void Apply()
|
||||
{
|
||||
if (m_TestSettings != null)
|
||||
{
|
||||
m_TestSettings.SetupProjectParameters();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (m_TestSettings != null)
|
||||
{
|
||||
m_TestSettings.Dispose();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,188 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor.TestRunner.CommandLineParser;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEditor.TestTools.TestRunner.GUI;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
internal class SettingsBuilder : ISettingsBuilder
|
||||
{
|
||||
private ITestSettingsDeserializer m_TestSettingsDeserializer;
|
||||
private Action<string> m_LogAction;
|
||||
private Action<string> m_LogWarningAction;
|
||||
private Func<string, bool> m_FileExistsCheck;
|
||||
private Func<bool> m_ScriptCompilationFailedCheck;
|
||||
public SettingsBuilder(ITestSettingsDeserializer testSettingsDeserializer, Action<string> logAction, Action<string> logWarningAction, Func<string, bool> fileExistsCheck, Func<bool> scriptCompilationFailedCheck)
|
||||
{
|
||||
m_LogAction = logAction;
|
||||
m_LogWarningAction = logWarningAction;
|
||||
m_FileExistsCheck = fileExistsCheck;
|
||||
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 testRepetitions = 1;
|
||||
int? playerHeartbeatTimeout = null;
|
||||
bool runSynchronously = false;
|
||||
string[] testAssemblyNames = null;
|
||||
|
||||
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("testRepetitions", reps => { testRepetitions = int.Parse(reps); }),
|
||||
new CommandLineOption("playerHeartbeatTimeout", timeout => { playerHeartbeatTimeout = int.Parse(timeout); }),
|
||||
new CommandLineOption("runSynchronously", () => { runSynchronously = true; }),
|
||||
new CommandLineOption("assemblyNames", assemblyNames => { testAssemblyNames = assemblyNames; })
|
||||
);
|
||||
optionSet.Parse(commandLineArgs);
|
||||
|
||||
DisplayQuitWarningIfQuitIsGiven(quit);
|
||||
|
||||
CheckForScriptCompilationErrors();
|
||||
|
||||
LogParametersForRun(testPlatform, testFilters, testCategories, testSettingsFilePath);
|
||||
|
||||
var testSettings = GetTestSettings(testSettingsFilePath);
|
||||
|
||||
var filter = new Filter()
|
||||
{
|
||||
groupNames = testFilters,
|
||||
categoryNames = testCategories,
|
||||
assemblyNames = testAssemblyNames
|
||||
};
|
||||
|
||||
var buildTarget = SetFilterAndGetBuildTarget(testPlatform, filter);
|
||||
|
||||
RerunCallbackData.instance.runFilters = new []{new UITestRunnerFilter()
|
||||
{
|
||||
categoryNames = filter.categoryNames,
|
||||
groupNames = filter.groupNames,
|
||||
testRepetitions = testRepetitions
|
||||
}};
|
||||
|
||||
RerunCallbackData.instance.testMode = filter.testMode;
|
||||
|
||||
var settings = new Api.ExecutionSettings()
|
||||
{
|
||||
filters = new []{filter},
|
||||
overloadTestRunSettings = new RunSettings(testSettings),
|
||||
targetPlatform = buildTarget,
|
||||
runSynchronously = runSynchronously
|
||||
};
|
||||
|
||||
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
|
||||
};
|
||||
}
|
||||
|
||||
void DisplayQuitWarningIfQuitIsGiven(bool quitIsGiven)
|
||||
{
|
||||
if (quitIsGiven)
|
||||
{
|
||||
m_LogWarningAction("Running tests from command line arguments will not work when \"quit\" is specified.");
|
||||
}
|
||||
}
|
||||
|
||||
void CheckForScriptCompilationErrors()
|
||||
{
|
||||
if (m_ScriptCompilationFailedCheck())
|
||||
{
|
||||
throw new SetupException(SetupException.ExceptionType.ScriptCompilationFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void LogParametersForRun(string testPlatform, string[] testFilters, string[] testCategories, string testSettingsFilePath)
|
||||
{
|
||||
m_LogAction("Running tests for " + testPlatform);
|
||||
if (testFilters != null && testFilters.Length > 0)
|
||||
{
|
||||
m_LogAction("With test filter: " + string.Join(", ", testFilters));
|
||||
}
|
||||
if (testCategories != null && testCategories.Length > 0)
|
||||
{
|
||||
m_LogAction("With test categories: " + string.Join(", ", testCategories));
|
||||
}
|
||||
if (!string.IsNullOrEmpty(testSettingsFilePath))
|
||||
{
|
||||
m_LogAction("With test settings file: " + testSettingsFilePath);
|
||||
}
|
||||
}
|
||||
|
||||
ITestSettings GetTestSettings(string testSettingsFilePath)
|
||||
{
|
||||
ITestSettings testSettings = null;
|
||||
if (!string.IsNullOrEmpty(testSettingsFilePath))
|
||||
{
|
||||
if (!m_FileExistsCheck(testSettingsFilePath))
|
||||
{
|
||||
throw new SetupException(SetupException.ExceptionType.TestSettingsFileNotFound, testSettingsFilePath);
|
||||
}
|
||||
|
||||
testSettings = m_TestSettingsDeserializer.GetSettingsFromJsonFile(testSettingsFilePath);
|
||||
}
|
||||
return testSettings;
|
||||
}
|
||||
|
||||
static BuildTarget? SetFilterAndGetBuildTarget(string testPlatform, Filter filter)
|
||||
{
|
||||
BuildTarget? buildTarget = null;
|
||||
if (testPlatform.ToLower() == "editmode")
|
||||
{
|
||||
filter.testMode = TestMode.EditMode;
|
||||
}
|
||||
else if (testPlatform.ToLower() == "playmode")
|
||||
{
|
||||
filter.testMode = TestMode.PlayMode;
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
buildTarget = (BuildTarget)Enum.Parse(typeof(BuildTarget), testPlatform, true);
|
||||
|
||||
filter.testMode = TestMode.PlayMode;
|
||||
}
|
||||
catch (ArgumentException)
|
||||
{
|
||||
throw new SetupException(SetupException.ExceptionType.PlatformNotFound, testPlatform);
|
||||
}
|
||||
}
|
||||
return buildTarget;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
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,
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,82 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor.TestRunner.CommandLineParser;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEditor.Compilation;
|
||||
using System.Linq;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.CommandLineTest
|
||||
{
|
||||
[InitializeOnLoad]
|
||||
static class TestStarter
|
||||
{
|
||||
static TestStarter()
|
||||
{
|
||||
if (!ShouldRunTests())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (EditorApplication.isCompiling)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (RunData.instance.isRunning)
|
||||
{
|
||||
executer.ExitOnCompileErrors();
|
||||
executer.SetUpCallbacks(RunData.instance.executionSettings);
|
||||
return;
|
||||
}
|
||||
|
||||
EditorApplication.update += UpdateWatch;
|
||||
}
|
||||
|
||||
static void UpdateWatch()
|
||||
{
|
||||
EditorApplication.update -= UpdateWatch;
|
||||
|
||||
if (RunData.instance.isRunning)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
RunData.instance.isRunning = true;
|
||||
var commandLineArgs = Environment.GetCommandLineArgs();
|
||||
RunData.instance.executionSettings = executer.BuildExecutionSettings(commandLineArgs);
|
||||
executer.SetUpCallbacks(RunData.instance.executionSettings);
|
||||
executer.InitializeAndExecuteRun(commandLineArgs);
|
||||
}
|
||||
|
||||
static bool ShouldRunTests()
|
||||
{
|
||||
var shouldRunTests = false;
|
||||
var optionSet = new CommandLineOptionSet(
|
||||
new CommandLineOption("runTests", () => { shouldRunTests = true; }),
|
||||
new CommandLineOption("runEditorTests", () => { shouldRunTests = true; })
|
||||
);
|
||||
optionSet.Parse(Environment.GetCommandLineArgs());
|
||||
return shouldRunTests;
|
||||
}
|
||||
|
||||
static Executer s_Executer;
|
||||
|
||||
static Executer executer
|
||||
{
|
||||
get
|
||||
{
|
||||
if (s_Executer == null)
|
||||
{
|
||||
Func<bool> compilationCheck = () => EditorUtility.scriptCompilationFailed;
|
||||
Action<string> actionLogger = (string msg) => { Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, msg); };
|
||||
var apiSettingsBuilder = new SettingsBuilder(new TestSettingsDeserializer(() => new TestSettings()), actionLogger, Debug.LogWarning, File.Exists, compilationCheck);
|
||||
s_Executer = new Executer(ScriptableObject.CreateInstance<TestRunnerApi>(), apiSettingsBuilder, Debug.LogErrorFormat, Debug.LogException, EditorApplication.Exit, compilationCheck);
|
||||
}
|
||||
|
||||
return s_Executer;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,138 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text.RegularExpressions;
|
||||
using Unity.CodeEditor;
|
||||
using UnityEditor.Utils;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class GuiHelper : IGuiHelper
|
||||
{
|
||||
public GuiHelper(IMonoCecilHelper monoCecilHelper, IAssetsDatabaseHelper assetsDatabaseHelper)
|
||||
{
|
||||
MonoCecilHelper = monoCecilHelper;
|
||||
AssetsDatabaseHelper = assetsDatabaseHelper;
|
||||
Editor = new DefaultExternalCodeEditor();
|
||||
GetCSFiles = (dirPath, fileExtension) =>
|
||||
{
|
||||
return Directory.GetFiles(dirPath, $"*{fileExtension}", SearchOption.AllDirectories)
|
||||
.Select(Paths.UnifyDirectorySeparator);
|
||||
};
|
||||
}
|
||||
internal Func<string, string, IEnumerable<string>> GetCSFiles;
|
||||
protected IMonoCecilHelper MonoCecilHelper { get; private set; }
|
||||
public IAssetsDatabaseHelper AssetsDatabaseHelper { get; private set; }
|
||||
public IExternalCodeEditor Editor { get; internal set; }
|
||||
private const string FileExtension = ".cs";
|
||||
|
||||
public void OpenScriptInExternalEditor(Type type, MethodInfo method)
|
||||
{
|
||||
var fileOpenInfo = GetFileOpenInfo(type, method);
|
||||
|
||||
if (string.IsNullOrEmpty(fileOpenInfo.FilePath))
|
||||
{
|
||||
Debug.LogWarning("Failed to open test method source code in external editor. Inconsistent filename and yield return operator in target method.");
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (fileOpenInfo.LineNumber == 1)
|
||||
{
|
||||
Debug.LogWarning("Failed to get a line number for unity test method. So please find it in opened file in external editor.");
|
||||
}
|
||||
|
||||
if (!fileOpenInfo.FilePath.Contains("Assets"))
|
||||
{
|
||||
Editor.OpenProject(fileOpenInfo.FilePath, fileOpenInfo.LineNumber, 1);
|
||||
}
|
||||
else
|
||||
{
|
||||
AssetsDatabaseHelper.OpenAssetInItsDefaultExternalEditor(fileOpenInfo.FilePath, fileOpenInfo.LineNumber);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public IFileOpenInfo GetFileOpenInfo(Type type, MethodInfo method)
|
||||
{
|
||||
var fileOpenInfo = MonoCecilHelper.TryGetCecilFileOpenInfo(type, method);
|
||||
if (string.IsNullOrEmpty(fileOpenInfo.FilePath))
|
||||
{
|
||||
var dirPath = Paths.UnifyDirectorySeparator(Application.dataPath);
|
||||
var allCsFiles = GetCSFiles(dirPath, FileExtension);
|
||||
|
||||
var fileName = allCsFiles.FirstOrDefault(x =>
|
||||
x.Split(Path.DirectorySeparatorChar).Last().Equals(string.Concat(GetTestFileName(type), FileExtension)));
|
||||
|
||||
fileOpenInfo.FilePath = fileName ?? string.Empty;
|
||||
}
|
||||
|
||||
if (!fileOpenInfo.FilePath.Contains("Assets"))
|
||||
{
|
||||
return fileOpenInfo;
|
||||
}
|
||||
fileOpenInfo.FilePath = FilePathToAssetsRelativeAndUnified(fileOpenInfo.FilePath);
|
||||
|
||||
return fileOpenInfo;
|
||||
}
|
||||
|
||||
internal static string GetTestFileName(Type type)
|
||||
{
|
||||
//This handles the case of a test in a nested class, getting the name of the base class
|
||||
if (type.FullName != null && type.Namespace!=null && type.FullName.Contains("+"))
|
||||
{
|
||||
var removedNamespace = type.FullName.Substring(type.Namespace.Length+1);
|
||||
return removedNamespace.Substring(0,removedNamespace.IndexOf("+", StringComparison.Ordinal));
|
||||
}
|
||||
return type.Name;
|
||||
}
|
||||
public string FilePathToAssetsRelativeAndUnified(string filePath)
|
||||
{
|
||||
if (string.IsNullOrEmpty(filePath))
|
||||
return string.Empty;
|
||||
|
||||
filePath = Paths.UnifyDirectorySeparator(filePath);
|
||||
var length = Paths.UnifyDirectorySeparator(Application.dataPath).Length - "Assets".Length;
|
||||
|
||||
return filePath.Substring(length);
|
||||
}
|
||||
|
||||
public bool OpenScriptInExternalEditor(string stacktrace)
|
||||
{
|
||||
if (string.IsNullOrEmpty(stacktrace))
|
||||
return false;
|
||||
|
||||
var regex = new Regex("in (?<path>.*):{1}(?<line>[0-9]+)");
|
||||
|
||||
var matchingLines = stacktrace.Split(new[] { Environment.NewLine }, StringSplitOptions.RemoveEmptyEntries).Where(x => regex.IsMatch(x)).ToList();
|
||||
if (!matchingLines.Any())
|
||||
return false;
|
||||
|
||||
var fileOpenInfos = matchingLines
|
||||
.Select(x => regex.Match(x))
|
||||
.Select(x =>
|
||||
new FileOpenInfo
|
||||
{
|
||||
FilePath = x.Groups["path"].Value,
|
||||
LineNumber = int.Parse(x.Groups["line"].Value)
|
||||
}).ToList();
|
||||
|
||||
var fileOpenInfo = fileOpenInfos
|
||||
.FirstOrDefault(openInfo => !string.IsNullOrEmpty(openInfo.FilePath) && File.Exists(openInfo.FilePath));
|
||||
|
||||
if (fileOpenInfo == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var filePath = FilePathToAssetsRelativeAndUnified(fileOpenInfo.FilePath);
|
||||
AssetsDatabaseHelper.OpenAssetInItsDefaultExternalEditor(filePath, fileOpenInfo.LineNumber);
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,7 @@
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal interface IAssetsDatabaseHelper
|
||||
{
|
||||
void OpenAssetInItsDefaultExternalEditor(string assetPath, int line);
|
||||
}
|
||||
}
|
@@ -0,0 +1,13 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal interface IGuiHelper
|
||||
{
|
||||
bool OpenScriptInExternalEditor(string stacktrace);
|
||||
void OpenScriptInExternalEditor(Type type, MethodInfo method);
|
||||
IFileOpenInfo GetFileOpenInfo(Type type, MethodInfo method);
|
||||
string FilePathToAssetsRelativeAndUnified(string filePath);
|
||||
}
|
||||
}
|
@@ -0,0 +1,12 @@
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class RenderingOptions
|
||||
{
|
||||
public string nameFilter;
|
||||
public bool showSucceeded;
|
||||
public bool showFailed;
|
||||
public bool showIgnored;
|
||||
public bool showNotRunned;
|
||||
public string[] categories;
|
||||
}
|
||||
}
|
@@ -0,0 +1,174 @@
|
||||
// ****************************************************************
|
||||
// Based on nUnit 2.6.2 (http://www.nunit.org/)
|
||||
// ****************************************************************
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
/// <summary>
|
||||
/// Summary description for ResultSummarizer.
|
||||
/// </summary>
|
||||
internal class ResultSummarizer
|
||||
{
|
||||
private int m_ErrorCount = -1;
|
||||
private int m_FailureCount;
|
||||
private int m_IgnoreCount = -1;
|
||||
private int m_InconclusiveCount = -1;
|
||||
private int m_NotRunnable = -1;
|
||||
private int m_ResultCount;
|
||||
private int m_SkipCount;
|
||||
private int m_SuccessCount;
|
||||
private int m_TestsRun;
|
||||
|
||||
private TimeSpan m_Duration = TimeSpan.FromSeconds(0);
|
||||
|
||||
public ResultSummarizer(IEnumerable<TestRunnerResult> results)
|
||||
{
|
||||
foreach (var result in results)
|
||||
Summarize(result);
|
||||
}
|
||||
|
||||
public bool success
|
||||
{
|
||||
get { return m_FailureCount == 0; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases for which results
|
||||
/// have been summarized. Any tests excluded by use of
|
||||
/// Category or Explicit attributes are not counted.
|
||||
/// </summary>
|
||||
public int ResultCount
|
||||
{
|
||||
get { return m_ResultCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases actually run, which
|
||||
/// is the same as ResultCount, less any Skipped, Ignored
|
||||
/// or NonRunnable tests.
|
||||
/// </summary>
|
||||
public int TestsRun
|
||||
{
|
||||
get { return m_TestsRun; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of tests that passed
|
||||
/// </summary>
|
||||
public int Passed
|
||||
{
|
||||
get { return m_SuccessCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases that had an error.
|
||||
/// </summary>
|
||||
public int errors
|
||||
{
|
||||
get { return m_ErrorCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases that failed.
|
||||
/// </summary>
|
||||
public int failures
|
||||
{
|
||||
get { return m_FailureCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases that failed.
|
||||
/// </summary>
|
||||
public int inconclusive
|
||||
{
|
||||
get { return m_InconclusiveCount; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases that were not runnable
|
||||
/// due to errors in the signature of the class or method.
|
||||
/// Such tests are also counted as Errors.
|
||||
/// </summary>
|
||||
public int notRunnable
|
||||
{
|
||||
get { return m_NotRunnable; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns the number of test cases that were skipped.
|
||||
/// </summary>
|
||||
public int Skipped
|
||||
{
|
||||
get { return m_SkipCount; }
|
||||
}
|
||||
|
||||
public int ignored
|
||||
{
|
||||
get { return m_IgnoreCount; }
|
||||
}
|
||||
|
||||
public double duration
|
||||
{
|
||||
get { return m_Duration.TotalSeconds; }
|
||||
}
|
||||
|
||||
public int testsNotRun
|
||||
{
|
||||
get { return m_SkipCount + m_IgnoreCount + m_NotRunnable; }
|
||||
}
|
||||
|
||||
public void Summarize(TestRunnerResult result)
|
||||
{
|
||||
m_Duration += TimeSpan.FromSeconds(result.duration);
|
||||
m_ResultCount++;
|
||||
|
||||
if (result.resultStatus != TestRunnerResult.ResultStatus.NotRun)
|
||||
{
|
||||
//TODO implement missing features
|
||||
// if(result.IsIgnored)
|
||||
// {
|
||||
// m_IgnoreCount++;
|
||||
// return;
|
||||
// }
|
||||
|
||||
m_SkipCount++;
|
||||
return;
|
||||
}
|
||||
|
||||
switch (result.resultStatus)
|
||||
{
|
||||
case TestRunnerResult.ResultStatus.Passed:
|
||||
m_SuccessCount++;
|
||||
m_TestsRun++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Failed:
|
||||
m_FailureCount++;
|
||||
m_TestsRun++;
|
||||
break;
|
||||
//TODO implement missing features
|
||||
// case TestResultState.Error:
|
||||
// case TestResultState.Cancelled:
|
||||
// m_ErrorCount++;
|
||||
// m_TestsRun++;
|
||||
// break;
|
||||
// case TestResultState.Inconclusive:
|
||||
// m_InconclusiveCount++;
|
||||
// m_TestsRun++;
|
||||
// break;
|
||||
// case TestResultState.NotRunnable:
|
||||
// m_NotRunnable++;
|
||||
// // errorCount++;
|
||||
// break;
|
||||
// case TestResultState.Ignored:
|
||||
// m_IgnoreCount++;
|
||||
// break;
|
||||
default:
|
||||
m_SkipCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,104 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestFilterSettings
|
||||
{
|
||||
public bool showSucceeded;
|
||||
public bool showFailed;
|
||||
public bool showIgnored;
|
||||
public bool showNotRun;
|
||||
|
||||
public string filterByName;
|
||||
public int filterByCategory;
|
||||
|
||||
private GUIContent m_SucceededBtn;
|
||||
private GUIContent m_FailedBtn;
|
||||
private GUIContent m_IgnoredBtn;
|
||||
private GUIContent m_NotRunBtn;
|
||||
|
||||
public string[] availableCategories;
|
||||
|
||||
private readonly string m_PrefsKey;
|
||||
|
||||
public TestFilterSettings(string prefsKey)
|
||||
{
|
||||
availableCategories = null;
|
||||
m_PrefsKey = prefsKey;
|
||||
Load();
|
||||
UpdateCounters(Enumerable.Empty<TestRunnerResult>());
|
||||
}
|
||||
|
||||
public void Load()
|
||||
{
|
||||
showSucceeded = EditorPrefs.GetBool(m_PrefsKey + ".ShowSucceeded", true);
|
||||
showFailed = EditorPrefs.GetBool(m_PrefsKey + ".ShowFailed", true);
|
||||
showIgnored = EditorPrefs.GetBool(m_PrefsKey + ".ShowIgnored", true);
|
||||
showNotRun = EditorPrefs.GetBool(m_PrefsKey + ".ShowNotRun", true);
|
||||
filterByName = EditorPrefs.GetString(m_PrefsKey + ".FilterByName", string.Empty);
|
||||
filterByCategory = EditorPrefs.GetInt(m_PrefsKey + ".FilterByCategory", 0);
|
||||
}
|
||||
|
||||
public void Save()
|
||||
{
|
||||
EditorPrefs.SetBool(m_PrefsKey + ".ShowSucceeded", showSucceeded);
|
||||
EditorPrefs.SetBool(m_PrefsKey + ".ShowFailed", showFailed);
|
||||
EditorPrefs.SetBool(m_PrefsKey + ".ShowIgnored", showIgnored);
|
||||
EditorPrefs.SetBool(m_PrefsKey + ".ShowNotRun", showNotRun);
|
||||
EditorPrefs.SetString(m_PrefsKey + ".FilterByName", filterByName);
|
||||
EditorPrefs.SetInt(m_PrefsKey + ".FilterByCategory", filterByCategory);
|
||||
}
|
||||
|
||||
public void UpdateCounters(IEnumerable<TestRunnerResult> results)
|
||||
{
|
||||
var summary = new ResultSummarizer(results);
|
||||
|
||||
m_SucceededBtn = new GUIContent(summary.Passed.ToString(), Icons.s_SuccessImg, "Show tests that succeeded");
|
||||
m_FailedBtn = new GUIContent((summary.errors + summary.failures + summary.inconclusive).ToString(), Icons.s_FailImg, "Show tests that failed");
|
||||
m_IgnoredBtn = new GUIContent((summary.ignored + summary.notRunnable).ToString(), Icons.s_IgnoreImg, "Show tests that are ignored");
|
||||
m_NotRunBtn = new GUIContent((summary.testsNotRun - summary.ignored - summary.notRunnable).ToString(), Icons.s_UnknownImg, "Show tests that didn't run");
|
||||
}
|
||||
|
||||
public string[] GetSelectedCategories()
|
||||
{
|
||||
if (availableCategories == null)
|
||||
return new string[0];
|
||||
|
||||
return availableCategories.Where((c, i) => (filterByCategory & (1 << i)) != 0).ToArray();
|
||||
}
|
||||
|
||||
public void OnGUI()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
|
||||
filterByName = GUILayout.TextField(filterByName, "ToolbarSeachTextField", GUILayout.MinWidth(100), GUILayout.MaxWidth(250), GUILayout.ExpandWidth(true));
|
||||
if (GUILayout.Button(GUIContent.none, string.IsNullOrEmpty(filterByName) ? "ToolbarSeachCancelButtonEmpty" : "ToolbarSeachCancelButton"))
|
||||
filterByName = string.Empty;
|
||||
|
||||
if (availableCategories != null && availableCategories.Length > 0)
|
||||
filterByCategory = EditorGUILayout.MaskField(filterByCategory, availableCategories, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(90));
|
||||
|
||||
showSucceeded = GUILayout.Toggle(showSucceeded, m_SucceededBtn, EditorStyles.toolbarButton);
|
||||
showFailed = GUILayout.Toggle(showFailed, m_FailedBtn, EditorStyles.toolbarButton);
|
||||
showIgnored = GUILayout.Toggle(showIgnored, m_IgnoredBtn, EditorStyles.toolbarButton);
|
||||
showNotRun = GUILayout.Toggle(showNotRun, m_NotRunBtn, EditorStyles.toolbarButton);
|
||||
|
||||
if (EditorGUI.EndChangeCheck())
|
||||
Save();
|
||||
}
|
||||
|
||||
public RenderingOptions BuildRenderingOptions()
|
||||
{
|
||||
var options = new RenderingOptions();
|
||||
options.showSucceeded = showSucceeded;
|
||||
options.showFailed = showFailed;
|
||||
options.showIgnored = showIgnored;
|
||||
options.showNotRunned = showNotRun;
|
||||
options.nameFilter = filterByName;
|
||||
options.categories = GetSelectedCategories();
|
||||
return options;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,110 @@
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine.TestRunner.NUnitExtensions;
|
||||
using UnityEngine.TestRunner.NUnitExtensions.Filters;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestTreeViewBuilder
|
||||
{
|
||||
public List<TestRunnerResult> results = new List<TestRunnerResult>();
|
||||
private readonly Dictionary<string, TestRunnerResult> m_OldTestResults;
|
||||
private readonly TestRunnerUIFilter m_UIFilter;
|
||||
private readonly ITestAdaptor m_TestListRoot;
|
||||
|
||||
private readonly List<string> m_AvailableCategories = new List<string>();
|
||||
|
||||
public string[] AvailableCategories
|
||||
{
|
||||
get { return m_AvailableCategories.Distinct().OrderBy(a => a).ToArray(); }
|
||||
}
|
||||
|
||||
public TestTreeViewBuilder(ITestAdaptor tests, Dictionary<string, TestRunnerResult> oldTestResultResults, TestRunnerUIFilter uiFilter)
|
||||
{
|
||||
m_AvailableCategories.Add(CategoryFilterExtended.k_DefaultCategory);
|
||||
m_OldTestResults = oldTestResultResults;
|
||||
m_TestListRoot = tests;
|
||||
m_UIFilter = uiFilter;
|
||||
}
|
||||
|
||||
public TreeViewItem BuildTreeView(TestFilterSettings settings, bool sceneBased, string sceneName)
|
||||
{
|
||||
var rootItem = new TreeViewItem(int.MaxValue, 0, null, "Invisible Root Item");
|
||||
ParseTestTree(0, rootItem, m_TestListRoot);
|
||||
return rootItem;
|
||||
}
|
||||
|
||||
private bool IsFilteredOutByUIFilter(ITestAdaptor test, TestRunnerResult result)
|
||||
{
|
||||
if (m_UIFilter.PassedHidden && result.resultStatus == TestRunnerResult.ResultStatus.Passed)
|
||||
return true;
|
||||
if (m_UIFilter.FailedHidden && (result.resultStatus == TestRunnerResult.ResultStatus.Failed || result.resultStatus == TestRunnerResult.ResultStatus.Inconclusive))
|
||||
return true;
|
||||
if (m_UIFilter.NotRunHidden && (result.resultStatus == TestRunnerResult.ResultStatus.NotRun || result.resultStatus == TestRunnerResult.ResultStatus.Skipped))
|
||||
return true;
|
||||
if (m_UIFilter.CategoryFilter.Length > 0)
|
||||
return !test.Categories.Any(category => m_UIFilter.CategoryFilter.Contains(category));
|
||||
return false;
|
||||
}
|
||||
|
||||
private void ParseTestTree(int depth, TreeViewItem rootItem, ITestAdaptor testElement)
|
||||
{
|
||||
m_AvailableCategories.AddRange(testElement.Categories);
|
||||
|
||||
var testElementId = testElement.UniqueName;
|
||||
if (!testElement.HasChildren)
|
||||
{
|
||||
m_OldTestResults.TryGetValue(testElementId, out var result);
|
||||
|
||||
if (result != null &&
|
||||
(result.ignoredOrSkipped
|
||||
|| result.notRunnable
|
||||
|| testElement.RunState == RunState.NotRunnable
|
||||
|| testElement.RunState == RunState.Ignored
|
||||
|| testElement.RunState == RunState.Skipped
|
||||
)
|
||||
)
|
||||
{
|
||||
//if the test was or becomes ignored or not runnable, we recreate the result in case it has changed
|
||||
result = null;
|
||||
}
|
||||
if (result == null)
|
||||
{
|
||||
result = new TestRunnerResult(testElement);
|
||||
}
|
||||
results.Add(result);
|
||||
|
||||
var test = new TestTreeViewItem(testElement, depth, rootItem);
|
||||
if (!IsFilteredOutByUIFilter(testElement, result))
|
||||
rootItem.AddChild(test);
|
||||
test.SetResult(result);
|
||||
return;
|
||||
}
|
||||
|
||||
m_OldTestResults.TryGetValue(testElementId, out var groupResult);
|
||||
if (groupResult == null)
|
||||
{
|
||||
groupResult = new TestRunnerResult(testElement);
|
||||
}
|
||||
|
||||
results.Add(groupResult);
|
||||
var group = new TestTreeViewItem(testElement, depth, rootItem);
|
||||
group.SetResult(groupResult);
|
||||
|
||||
depth++;
|
||||
foreach (var child in testElement.Children)
|
||||
{
|
||||
ParseTestTree(depth, group, child);
|
||||
}
|
||||
|
||||
|
||||
if (testElement.IsTestAssembly && !testElement.HasChildren)
|
||||
return;
|
||||
|
||||
if (group.hasChildren)
|
||||
rootItem.AddChild(group);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,135 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.ProjectWindowCallback;
|
||||
using UnityEditor.Scripting.ScriptCompilation;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestListGUIHelper
|
||||
{
|
||||
private const string kResourcesTemplatePath = "Resources/ScriptTemplates";
|
||||
private const string kAssemblyDefinitionTestTemplate = "92-Assembly Definition-NewTestAssembly.asmdef.txt";
|
||||
|
||||
private const string kAssemblyDefinitionEditModeTestTemplate =
|
||||
"92-Assembly Definition-NewEditModeTestAssembly.asmdef.txt";
|
||||
|
||||
private const string kTestScriptTemplate = "83-C# Script-NewTestScript.cs.txt";
|
||||
private const string kNewTestScriptName = "NewTestScript.cs";
|
||||
private const string kNunit = "nunit.framework.dll";
|
||||
|
||||
[MenuItem("Assets/Create/Testing/Tests Assembly Folder", false, 83)]
|
||||
public static void MenuItemAddFolderAndAsmDefForTesting()
|
||||
{
|
||||
AddFolderAndAsmDefForTesting();
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/Testing/Tests Assembly Folder", true, 83)]
|
||||
public static bool MenuItemAddFolderAndAsmDefForTestingWithValidation()
|
||||
{
|
||||
return !SelectedFolderContainsTestAssembly();
|
||||
}
|
||||
|
||||
public static void AddFolderAndAsmDefForTesting(bool isEditorOnly = false)
|
||||
{
|
||||
ProjectWindowUtil.CreateFolderWithTemplates("Tests",
|
||||
isEditorOnly ? kAssemblyDefinitionEditModeTestTemplate : kAssemblyDefinitionTestTemplate);
|
||||
}
|
||||
|
||||
public static bool SelectedFolderContainsTestAssembly()
|
||||
{
|
||||
var theNearestCustomScriptAssembly = GetTheNearestCustomScriptAssembly();
|
||||
if (theNearestCustomScriptAssembly != null)
|
||||
{
|
||||
return theNearestCustomScriptAssembly.PrecompiledReferences != null && theNearestCustomScriptAssembly.PrecompiledReferences.Any(x => Path.GetFileName(x) == kNunit);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/Testing/C# Test Script", false, 83)]
|
||||
public static void AddTest()
|
||||
{
|
||||
var basePath = Path.Combine(EditorApplication.applicationContentsPath, kResourcesTemplatePath);
|
||||
var destPath = Path.Combine(GetActiveFolderPath(), kNewTestScriptName);
|
||||
var templatePath = Path.Combine(basePath, kTestScriptTemplate);
|
||||
var icon = EditorGUIUtility.IconContent("cs Script Icon").image as Texture2D;
|
||||
ProjectWindowUtil.StartNameEditingIfProjectWindowExists(0,
|
||||
ScriptableObject.CreateInstance<DoCreateScriptAsset>(), destPath, icon, templatePath);
|
||||
|
||||
AssetDatabase.Refresh();
|
||||
}
|
||||
|
||||
[MenuItem("Assets/Create/Testing/C# Test Script", true, 83)]
|
||||
public static bool CanAddScriptAndItWillCompile()
|
||||
{
|
||||
return CanAddEditModeTestScriptAndItWillCompile() || CanAddPlayModeTestScriptAndItWillCompile();
|
||||
}
|
||||
|
||||
public static bool CanAddEditModeTestScriptAndItWillCompile()
|
||||
{
|
||||
var theNearestCustomScriptAssembly = GetTheNearestCustomScriptAssembly();
|
||||
if (theNearestCustomScriptAssembly != null)
|
||||
{
|
||||
return (theNearestCustomScriptAssembly.AssemblyFlags & AssemblyFlags.EditorOnly) ==
|
||||
AssemblyFlags.EditorOnly;
|
||||
}
|
||||
|
||||
var activeFolderPath = GetActiveFolderPath();
|
||||
return activeFolderPath.ToLower().Contains("/editor");
|
||||
}
|
||||
|
||||
public static bool CanAddPlayModeTestScriptAndItWillCompile()
|
||||
{
|
||||
if (PlayerSettings.playModeTestRunnerEnabled)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var theNearestCustomScriptAssembly = GetTheNearestCustomScriptAssembly();
|
||||
|
||||
if (theNearestCustomScriptAssembly == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
var hasTestAssemblyFlag = theNearestCustomScriptAssembly.PrecompiledReferences != null && theNearestCustomScriptAssembly.PrecompiledReferences.Any(x => Path.GetFileName(x) == kNunit);;
|
||||
var editorOnlyAssembly = (theNearestCustomScriptAssembly.AssemblyFlags & AssemblyFlags.EditorOnly) != 0;
|
||||
|
||||
return hasTestAssemblyFlag && !editorOnlyAssembly;
|
||||
}
|
||||
|
||||
public static string GetActiveFolderPath()
|
||||
{
|
||||
var path = "Assets";
|
||||
|
||||
foreach (var obj in Selection.GetFiltered(typeof(UnityEngine.Object), SelectionMode.Assets))
|
||||
{
|
||||
path = AssetDatabase.GetAssetPath(obj);
|
||||
if (!string.IsNullOrEmpty(path) && File.Exists(path))
|
||||
{
|
||||
path = Path.GetDirectoryName(path);
|
||||
break;
|
||||
}
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
private static CustomScriptAssembly GetTheNearestCustomScriptAssembly()
|
||||
{
|
||||
CustomScriptAssembly findCustomScriptAssemblyFromScriptPath;
|
||||
try
|
||||
{
|
||||
findCustomScriptAssemblyFromScriptPath =
|
||||
EditorCompilationInterface.Instance.FindCustomScriptAssemblyFromScriptPath(
|
||||
Path.Combine(GetActiveFolderPath(), "Foo.cs"));
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
return findCustomScriptAssemblyFromScriptPath;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,24 @@
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal static class Icons
|
||||
{
|
||||
public static readonly Texture2D s_FailImg;
|
||||
public static readonly Texture2D s_IgnoreImg;
|
||||
public static readonly Texture2D s_SuccessImg;
|
||||
public static readonly Texture2D s_UnknownImg;
|
||||
public static readonly Texture2D s_InconclusiveImg;
|
||||
public static readonly Texture2D s_StopwatchImg;
|
||||
|
||||
static Icons()
|
||||
{
|
||||
s_FailImg = EditorGUIUtility.IconContent("TestFailed").image as Texture2D;
|
||||
s_IgnoreImg = EditorGUIUtility.IconContent("TestIgnored").image as Texture2D;
|
||||
s_SuccessImg = EditorGUIUtility.IconContent("TestPassed").image as Texture2D;
|
||||
s_UnknownImg = EditorGUIUtility.IconContent("TestNormal").image as Texture2D;
|
||||
s_InconclusiveImg = EditorGUIUtility.IconContent("TestInconclusive").image as Texture2D;
|
||||
s_StopwatchImg = EditorGUIUtility.IconContent("TestStopwatch").image as Texture2D;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,98 @@
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestListTreeViewDataSource : TreeViewDataSource
|
||||
{
|
||||
private bool m_ExpandTreeOnCreation;
|
||||
private readonly TestListGUI m_TestListGUI;
|
||||
private ITestAdaptor m_RootTest;
|
||||
|
||||
public TestListTreeViewDataSource(TreeViewController testListTree, TestListGUI testListGUI, ITestAdaptor rootTest) : base(testListTree)
|
||||
{
|
||||
showRootItem = false;
|
||||
rootIsCollapsable = false;
|
||||
m_TestListGUI = testListGUI;
|
||||
m_RootTest = rootTest;
|
||||
}
|
||||
|
||||
public void UpdateRootTest(ITestAdaptor rootTest)
|
||||
{
|
||||
m_RootTest = rootTest;
|
||||
}
|
||||
|
||||
public override void FetchData()
|
||||
{
|
||||
var sceneName = SceneManager.GetActiveScene().name;
|
||||
if (sceneName.StartsWith("InitTestScene"))
|
||||
sceneName = PlaymodeTestsController.GetController().settings.originalScene;
|
||||
|
||||
var testListBuilder = new TestTreeViewBuilder(m_RootTest, m_TestListGUI.ResultsByKey, m_TestListGUI.m_TestRunnerUIFilter);
|
||||
|
||||
m_RootItem = testListBuilder.BuildTreeView(null, false, sceneName);
|
||||
SetExpanded(m_RootItem, true);
|
||||
if (m_RootItem.hasChildren && m_RootItem.children.Count == 1)
|
||||
SetExpanded(m_RootItem.children[0], true);
|
||||
|
||||
if (m_ExpandTreeOnCreation)
|
||||
SetExpandedWithChildren(m_RootItem, true);
|
||||
|
||||
m_TestListGUI.newResultList = new List<TestRunnerResult>(testListBuilder.results);
|
||||
m_TestListGUI.m_TestRunnerUIFilter.availableCategories = testListBuilder.AvailableCategories;
|
||||
m_NeedRefreshRows = true;
|
||||
}
|
||||
|
||||
public override bool IsRenamingItemAllowed(TreeViewItem item)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
public void ExpandTreeOnCreation()
|
||||
{
|
||||
m_ExpandTreeOnCreation = true;
|
||||
}
|
||||
|
||||
public override bool IsExpandable(TreeViewItem item)
|
||||
{
|
||||
if (item is TestTreeViewItem)
|
||||
return ((TestTreeViewItem)item).IsGroupNode;
|
||||
return base.IsExpandable(item);
|
||||
}
|
||||
|
||||
protected override List<TreeViewItem> Search(TreeViewItem rootItem, string search)
|
||||
{
|
||||
var result = new List<TreeViewItem>();
|
||||
|
||||
if (rootItem.hasChildren)
|
||||
{
|
||||
foreach (var child in rootItem.children)
|
||||
{
|
||||
SearchTestTree(child, search, result);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
protected void SearchTestTree(TreeViewItem item, string search, IList<TreeViewItem> searchResult)
|
||||
{
|
||||
var testItem = item as TestTreeViewItem;
|
||||
if (!testItem.IsGroupNode)
|
||||
{
|
||||
if (testItem.FullName.ToLower().Contains(search))
|
||||
{
|
||||
searchResult.Add(item);
|
||||
}
|
||||
}
|
||||
else if (item.children != null)
|
||||
{
|
||||
foreach (var child in item.children)
|
||||
SearchTestTree(child, search, searchResult);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal class TestListTreeViewGUI : TreeViewGUI
|
||||
{
|
||||
public TestListTreeViewGUI(TreeViewController testListTree) : base(testListTree)
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,137 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal sealed class TestTreeViewItem : TreeViewItem
|
||||
{
|
||||
public TestRunnerResult result;
|
||||
internal ITestAdaptor m_Test;
|
||||
|
||||
public Type type;
|
||||
public MethodInfo method;
|
||||
|
||||
private const int k_ResultTestMaxLength = 15000;
|
||||
|
||||
public bool IsGroupNode { get { return m_Test.IsSuite; } }
|
||||
|
||||
public string FullName { get { return m_Test.FullName; } }
|
||||
|
||||
public string GetAssemblyName()
|
||||
{
|
||||
var test = m_Test;
|
||||
while (test != null)
|
||||
{
|
||||
if (test.IsTestAssembly)
|
||||
{
|
||||
return test.FullName;
|
||||
}
|
||||
|
||||
test = test.Parent;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public TestTreeViewItem(ITestAdaptor test, int depth, TreeViewItem parent)
|
||||
: base(GetId(test), depth, parent, test.Name)
|
||||
{
|
||||
m_Test = test;
|
||||
|
||||
if (test.TypeInfo != null)
|
||||
{
|
||||
type = test.TypeInfo.Type;
|
||||
}
|
||||
if (test.Method != null)
|
||||
{
|
||||
method = test.Method.MethodInfo;
|
||||
}
|
||||
|
||||
displayName = test.Name.Replace("\n", "");
|
||||
icon = Icons.s_UnknownImg;
|
||||
}
|
||||
|
||||
private static int GetId(ITestAdaptor test)
|
||||
{
|
||||
return test.UniqueName.GetHashCode();
|
||||
}
|
||||
|
||||
public void SetResult(TestRunnerResult testResult)
|
||||
{
|
||||
result = testResult;
|
||||
result.SetResultChangedCallback(ResultUpdated);
|
||||
ResultUpdated(result);
|
||||
}
|
||||
|
||||
public string GetResultText()
|
||||
{
|
||||
if (result.resultStatus == TestRunnerResult.ResultStatus.NotRun)
|
||||
{
|
||||
return string.Empty;
|
||||
}
|
||||
var durationString = String.Format("{0:0.000}", result.duration);
|
||||
var sb = new StringBuilder(string.Format("{0} ({1}s)", displayName.Trim(), durationString));
|
||||
if (!string.IsNullOrEmpty(result.description))
|
||||
{
|
||||
sb.AppendFormat("\n{0}", result.description);
|
||||
}
|
||||
if (!string.IsNullOrEmpty(result.messages))
|
||||
{
|
||||
sb.Append("\n---\n");
|
||||
sb.Append(result.messages.Trim());
|
||||
}
|
||||
if (!string.IsNullOrEmpty(result.stacktrace))
|
||||
{
|
||||
sb.Append("\n---\n");
|
||||
sb.Append(result.stacktrace.Trim());
|
||||
}
|
||||
if (!string.IsNullOrEmpty(result.output))
|
||||
{
|
||||
sb.Append("\n---\n");
|
||||
sb.Append(result.output.Trim());
|
||||
}
|
||||
if (sb.Length > k_ResultTestMaxLength)
|
||||
{
|
||||
sb.Length = k_ResultTestMaxLength;
|
||||
sb.AppendFormat("...\n\n---MESSAGE TRUNCATED AT {0} CHARACTERS---", k_ResultTestMaxLength);
|
||||
}
|
||||
return sb.ToString().Trim();
|
||||
}
|
||||
|
||||
private void ResultUpdated(TestRunnerResult testResult)
|
||||
{
|
||||
switch (testResult.resultStatus)
|
||||
{
|
||||
case TestRunnerResult.ResultStatus.Passed:
|
||||
icon = Icons.s_SuccessImg;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Failed:
|
||||
icon = Icons.s_FailImg;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Inconclusive:
|
||||
icon = Icons.s_InconclusiveImg;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Skipped:
|
||||
icon = Icons.s_IgnoreImg;
|
||||
break;
|
||||
default:
|
||||
if (testResult.ignoredOrSkipped)
|
||||
{
|
||||
icon = Icons.s_IgnoreImg;
|
||||
}
|
||||
else if (testResult.notRunnable)
|
||||
{
|
||||
icon = Icons.s_FailImg;
|
||||
}
|
||||
else
|
||||
{
|
||||
icon = Icons.s_UnknownImg;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,148 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestRunnerResult : UITestRunnerFilter.IClearableResult
|
||||
{
|
||||
public string id;
|
||||
public string uniqueId;
|
||||
public string name;
|
||||
public string fullName;
|
||||
public ResultStatus resultStatus = ResultStatus.NotRun;
|
||||
public float duration;
|
||||
public string messages;
|
||||
public string output;
|
||||
public string stacktrace;
|
||||
public bool notRunnable;
|
||||
public bool ignoredOrSkipped;
|
||||
public string description;
|
||||
public bool isSuite;
|
||||
public List<string> categories;
|
||||
public string parentId;
|
||||
public string parentUniqueId;
|
||||
|
||||
//This field is suppose to mark results from before domain reload
|
||||
//Such result is outdated because the code might haev changed
|
||||
//This field will get reset every time a domain reload happens
|
||||
[NonSerialized]
|
||||
public bool notOutdated;
|
||||
|
||||
protected Action<TestRunnerResult> m_OnResultUpdate;
|
||||
|
||||
internal TestRunnerResult(ITestAdaptor test)
|
||||
{
|
||||
id = test.Id;
|
||||
uniqueId = test.UniqueName;
|
||||
|
||||
fullName = test.FullName;
|
||||
name = test.Name;
|
||||
description = test.Description;
|
||||
isSuite = test.IsSuite;
|
||||
|
||||
ignoredOrSkipped = test.RunState == RunState.Ignored || test.RunState == RunState.Skipped;
|
||||
notRunnable = test.RunState == RunState.NotRunnable;
|
||||
|
||||
if (ignoredOrSkipped)
|
||||
{
|
||||
messages = test.SkipReason;
|
||||
}
|
||||
if (notRunnable)
|
||||
{
|
||||
resultStatus = ResultStatus.Failed;
|
||||
messages = test.SkipReason;
|
||||
}
|
||||
categories = test.Categories.ToList();
|
||||
parentId = test.ParentId;
|
||||
parentUniqueId = test.ParentUniqueName;
|
||||
}
|
||||
|
||||
internal TestRunnerResult(ITestResultAdaptor testResult) : this(testResult.Test)
|
||||
{
|
||||
notOutdated = true;
|
||||
|
||||
messages = testResult.Message;
|
||||
output = testResult.Output;
|
||||
stacktrace = testResult.StackTrace;
|
||||
duration = (float)testResult.Duration;
|
||||
if (testResult.Test.IsSuite && testResult.ResultState == "Ignored")
|
||||
{
|
||||
resultStatus = ResultStatus.Passed;
|
||||
}
|
||||
else
|
||||
{
|
||||
resultStatus = ParseNUnitResultStatus(testResult.TestStatus);
|
||||
}
|
||||
}
|
||||
|
||||
public void Update(TestRunnerResult result)
|
||||
{
|
||||
if (ReferenceEquals(result, null))
|
||||
return;
|
||||
resultStatus = result.resultStatus;
|
||||
duration = result.duration;
|
||||
messages = result.messages;
|
||||
output = result.output;
|
||||
stacktrace = result.stacktrace;
|
||||
ignoredOrSkipped = result.ignoredOrSkipped;
|
||||
notRunnable = result.notRunnable;
|
||||
description = result.description;
|
||||
notOutdated = result.notOutdated;
|
||||
if (m_OnResultUpdate != null)
|
||||
m_OnResultUpdate(this);
|
||||
}
|
||||
|
||||
public void SetResultChangedCallback(Action<TestRunnerResult> resultUpdated)
|
||||
{
|
||||
m_OnResultUpdate = resultUpdated;
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal enum ResultStatus
|
||||
{
|
||||
NotRun,
|
||||
Passed,
|
||||
Failed,
|
||||
Inconclusive,
|
||||
Skipped
|
||||
}
|
||||
|
||||
private static ResultStatus ParseNUnitResultStatus(TestStatus status)
|
||||
{
|
||||
switch (status)
|
||||
{
|
||||
case TestStatus.Passed:
|
||||
return ResultStatus.Passed;
|
||||
case TestStatus.Failed:
|
||||
return ResultStatus.Failed;
|
||||
case TestStatus.Inconclusive:
|
||||
return ResultStatus.Inconclusive;
|
||||
case TestStatus.Skipped:
|
||||
return ResultStatus.Skipped;
|
||||
default:
|
||||
return ResultStatus.NotRun;
|
||||
}
|
||||
}
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
return string.Format("{0} ({1})", name, fullName);
|
||||
}
|
||||
|
||||
public string Id { get { return uniqueId; } }
|
||||
public string FullName { get { return fullName; } }
|
||||
public string ParentId { get { return parentUniqueId; } }
|
||||
public bool IsSuite { get { return isSuite; } }
|
||||
public List<string> Categories { get { return categories; } }
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
resultStatus = ResultStatus.NotRun;
|
||||
if (m_OnResultUpdate != null)
|
||||
m_OnResultUpdate(this);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,177 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestRunnerUIFilter
|
||||
{
|
||||
private int m_PassedCount;
|
||||
private int m_FailedCount;
|
||||
private int m_NotRunCount;
|
||||
private int m_InconclusiveCount;
|
||||
private int m_SkippedCount;
|
||||
|
||||
public int PassedCount { get { return m_PassedCount; } }
|
||||
public int FailedCount { get { return m_FailedCount + m_InconclusiveCount; } }
|
||||
public int NotRunCount { get { return m_NotRunCount + m_SkippedCount; } }
|
||||
|
||||
[SerializeField]
|
||||
public bool PassedHidden;
|
||||
[SerializeField]
|
||||
public bool FailedHidden;
|
||||
[SerializeField]
|
||||
public bool NotRunHidden;
|
||||
|
||||
[SerializeField]
|
||||
private string m_SearchString;
|
||||
[SerializeField]
|
||||
private int selectedCategoryMask;
|
||||
|
||||
public string[] availableCategories = new string[0];
|
||||
|
||||
|
||||
private GUIContent m_SucceededBtn;
|
||||
private GUIContent m_FailedBtn;
|
||||
private GUIContent m_NotRunBtn;
|
||||
|
||||
public Action RebuildTestList;
|
||||
public Action<string> SearchStringChanged;
|
||||
public Action SearchStringCleared;
|
||||
public bool IsFiltering
|
||||
{
|
||||
get
|
||||
{
|
||||
return !string.IsNullOrEmpty(m_SearchString) || PassedHidden || FailedHidden || NotRunHidden ||
|
||||
selectedCategoryMask != 0;
|
||||
}
|
||||
}
|
||||
|
||||
public string[] CategoryFilter
|
||||
{
|
||||
get
|
||||
{
|
||||
var list = new List<string>();
|
||||
for (int i = 0; i < availableCategories.Length; i++)
|
||||
{
|
||||
if ((selectedCategoryMask & (1 << i)) != 0)
|
||||
{
|
||||
list.Add(availableCategories[i]);
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateCounters(List<TestRunnerResult> resultList)
|
||||
{
|
||||
m_PassedCount = m_FailedCount = m_NotRunCount = m_InconclusiveCount = m_SkippedCount = 0;
|
||||
foreach (var result in resultList)
|
||||
{
|
||||
if (result.isSuite)
|
||||
continue;
|
||||
switch (result.resultStatus)
|
||||
{
|
||||
case TestRunnerResult.ResultStatus.Passed:
|
||||
m_PassedCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Failed:
|
||||
m_FailedCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Inconclusive:
|
||||
m_InconclusiveCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.Skipped:
|
||||
m_SkippedCount++;
|
||||
break;
|
||||
case TestRunnerResult.ResultStatus.NotRun:
|
||||
default:
|
||||
m_NotRunCount++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
var succeededTooltip = string.Format("Show tests that succeeded\n{0} succeeded", m_PassedCount);
|
||||
m_SucceededBtn = new GUIContent(PassedCount.ToString(), Icons.s_SuccessImg, succeededTooltip);
|
||||
var failedTooltip = string.Format("Show tests that failed\n{0} failed\n{1} inconclusive", m_FailedCount, m_InconclusiveCount);
|
||||
m_FailedBtn = new GUIContent(FailedCount.ToString(), Icons.s_FailImg, failedTooltip);
|
||||
var notRunTooltip = string.Format("Show tests that didn't run\n{0} didn't run\n{1} skipped or ignored", m_NotRunCount, m_SkippedCount);
|
||||
m_NotRunBtn = new GUIContent(NotRunCount.ToString(), Icons.s_UnknownImg, notRunTooltip);
|
||||
}
|
||||
|
||||
public void Draw()
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (m_SearchString == null)
|
||||
{
|
||||
m_SearchString = "";
|
||||
}
|
||||
m_SearchString = EditorGUILayout.ToolbarSearchField(m_SearchString);
|
||||
if (EditorGUI.EndChangeCheck() && SearchStringChanged != null)
|
||||
{
|
||||
SearchStringChanged(m_SearchString);
|
||||
if (String.IsNullOrEmpty(m_SearchString))
|
||||
SearchStringCleared();
|
||||
}
|
||||
|
||||
if (availableCategories != null && availableCategories.Any())
|
||||
{
|
||||
EditorGUI.BeginChangeCheck();
|
||||
selectedCategoryMask = EditorGUILayout.MaskField(selectedCategoryMask, availableCategories, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(150));
|
||||
if (EditorGUI.EndChangeCheck() && RebuildTestList != null)
|
||||
{
|
||||
RebuildTestList();
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
EditorGUILayout.Popup(0, new[] { "<No categories available>" }, EditorStyles.toolbarDropDown, GUILayout.MaxWidth(150));
|
||||
}
|
||||
|
||||
EditorGUI.BeginChangeCheck();
|
||||
if (m_SucceededBtn != null)
|
||||
{
|
||||
PassedHidden = !GUILayout.Toggle(!PassedHidden, m_SucceededBtn, EditorStyles.toolbarButton, GUILayout.MaxWidth(GetMaxWidth(PassedCount)));
|
||||
}
|
||||
if (m_FailedBtn != null)
|
||||
{
|
||||
FailedHidden = !GUILayout.Toggle(!FailedHidden, m_FailedBtn, EditorStyles.toolbarButton, GUILayout.MaxWidth(GetMaxWidth(FailedCount)));
|
||||
}
|
||||
if (m_NotRunBtn != null)
|
||||
{
|
||||
NotRunHidden = !GUILayout.Toggle(!NotRunHidden, m_NotRunBtn, EditorStyles.toolbarButton, GUILayout.MaxWidth(GetMaxWidth(NotRunCount)));
|
||||
}
|
||||
|
||||
if (EditorGUI.EndChangeCheck() && RebuildTestList != null)
|
||||
{
|
||||
RebuildTestList();
|
||||
}
|
||||
}
|
||||
|
||||
private static int GetMaxWidth(int count)
|
||||
{
|
||||
if (count < 10)
|
||||
return 33;
|
||||
return count < 100 ? 40 : 47;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
PassedHidden = false;
|
||||
FailedHidden = false;
|
||||
NotRunHidden = false;
|
||||
selectedCategoryMask = 0;
|
||||
m_SearchString = "";
|
||||
if (SearchStringChanged != null)
|
||||
{
|
||||
SearchStringChanged(m_SearchString);
|
||||
}
|
||||
if (SearchStringCleared != null)
|
||||
{
|
||||
SearchStringCleared();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,164 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class UITestRunnerFilter
|
||||
{
|
||||
#pragma warning disable 649
|
||||
public string[] assemblyNames;
|
||||
public string[] groupNames;
|
||||
public string[] categoryNames;
|
||||
public string[] testNames;
|
||||
public int testRepetitions = 1;
|
||||
public bool synchronousOnly = false;
|
||||
|
||||
public static string AssemblyNameFromPath(string path)
|
||||
{
|
||||
string output = Path.GetFileName(path);
|
||||
if (output != null && output.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
return output.Substring(0, output.Length - 4);
|
||||
return output;
|
||||
}
|
||||
|
||||
private bool CategoryMatches(IEnumerable<string> categories)
|
||||
{
|
||||
if (categoryNames == null || categoryNames.Length == 0)
|
||||
return true;
|
||||
|
||||
foreach (string category in categories)
|
||||
{
|
||||
if (categoryNames.Contains(category))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool IDMatchesAssembly(string id)
|
||||
{
|
||||
if (AreOptionalFiltersEmpty())
|
||||
return true;
|
||||
|
||||
if (assemblyNames == null || assemblyNames.Length == 0)
|
||||
return true;
|
||||
|
||||
int openingBracket = id.IndexOf('[');
|
||||
int closingBracket = id.IndexOf(']');
|
||||
if (openingBracket >= 0 && openingBracket < id.Length && closingBracket > openingBracket &&
|
||||
openingBracket < id.Length)
|
||||
{
|
||||
//Some assemblies are absolute and explicitly part of the test ID e.g.
|
||||
//"[/path/to/assembly-name.dll][rest of ID ...]"
|
||||
//While some are minimal assembly names e.g.
|
||||
//"[assembly-name][rest of ID ...]"
|
||||
//Strip them down to just the assembly name
|
||||
string assemblyNameFromID =
|
||||
AssemblyNameFromPath(id.Substring(openingBracket + 1, closingBracket - openingBracket - 1));
|
||||
foreach (string assemblyName in assemblyNames)
|
||||
{
|
||||
if (assemblyName.Equals(assemblyNameFromID, StringComparison.OrdinalIgnoreCase))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool NameMatches(string name)
|
||||
{
|
||||
if (AreOptionalFiltersEmpty())
|
||||
return true;
|
||||
|
||||
if (groupNames == null || groupNames.Length == 0)
|
||||
return true;
|
||||
|
||||
foreach (var nameFromFilter in groupNames)
|
||||
{
|
||||
//Strict regex match for test group name on its own
|
||||
if (Regex.IsMatch(name, nameFromFilter))
|
||||
return true;
|
||||
//Match test names that end with parametrized test values and full nunit generated test names that have . separators
|
||||
var regex = nameFromFilter.TrimEnd('$') + @"[\.|\(.*\)]";
|
||||
if (Regex.IsMatch(name, regex))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private bool AreOptionalFiltersEmpty()
|
||||
{
|
||||
if (assemblyNames != null && assemblyNames.Length != 0)
|
||||
return false;
|
||||
if (groupNames != null && groupNames.Length != 0)
|
||||
return false;
|
||||
if (testNames != null && testNames.Length != 0)
|
||||
return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
private bool NameMatchesExactly(string name)
|
||||
{
|
||||
if (AreOptionalFiltersEmpty())
|
||||
return true;
|
||||
|
||||
if (testNames == null || testNames.Length == 0)
|
||||
return true;
|
||||
|
||||
foreach (var exactName in testNames)
|
||||
{
|
||||
if (name == exactName)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private static void ClearAncestors(IEnumerable<IClearableResult> newResultList, string parentID)
|
||||
{
|
||||
if (string.IsNullOrEmpty(parentID))
|
||||
return;
|
||||
foreach (var result in newResultList)
|
||||
{
|
||||
if (result.Id == parentID)
|
||||
{
|
||||
result.Clear();
|
||||
ClearAncestors(newResultList, result.ParentId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void ClearResults(List<IClearableResult> newResultList)
|
||||
{
|
||||
foreach (var result in newResultList)
|
||||
{
|
||||
if (!result.IsSuite && CategoryMatches(result.Categories))
|
||||
{
|
||||
if (IDMatchesAssembly(result.Id) && NameMatches(result.FullName) &&
|
||||
NameMatchesExactly(result.FullName))
|
||||
{
|
||||
result.Clear();
|
||||
ClearAncestors(newResultList, result.ParentId);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal interface IClearableResult
|
||||
{
|
||||
string Id { get; }
|
||||
string FullName { get; }
|
||||
string ParentId { get; }
|
||||
bool IsSuite { get; }
|
||||
List<string> Categories { get; }
|
||||
void Clear();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class EditModeTestListGUI : TestListGUI
|
||||
{
|
||||
public override TestMode TestMode
|
||||
{
|
||||
get { return TestMode.EditMode; }
|
||||
}
|
||||
|
||||
public override void RenderNoTestsInfo()
|
||||
{
|
||||
if (!TestListGUIHelper.SelectedFolderContainsTestAssembly())
|
||||
{
|
||||
var noTestText = "No tests to show";
|
||||
|
||||
if (!PlayerSettings.playModeTestRunnerEnabled)
|
||||
{
|
||||
const string testsArePulledFromCustomAssemblies =
|
||||
"EditMode tests can be in Editor only Assemblies, either in the editor special folder or Editor only Assembly Definitions that references the \"nunit.framework.dll\" Assembly Reference or any of the Assembly Definition References \"UnityEngine.TestRunner\" or \"UnityEditor.TestRunner\"..";
|
||||
noTestText += Environment.NewLine + testsArePulledFromCustomAssemblies;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(noTestText, MessageType.Info);
|
||||
if (GUILayout.Button("Create EditMode Test Assembly Folder"))
|
||||
{
|
||||
TestListGUIHelper.AddFolderAndAsmDefForTesting(isEditorOnly: true);
|
||||
}
|
||||
}
|
||||
|
||||
if (!TestListGUIHelper.CanAddEditModeTestScriptAndItWillCompile())
|
||||
{
|
||||
UnityEngine.GUI.enabled = false;
|
||||
EditorGUILayout.HelpBox("EditMode test scripts can only be created in editor test assemblies.", MessageType.Warning);
|
||||
}
|
||||
if (GUILayout.Button("Create Test Script in current folder"))
|
||||
{
|
||||
TestListGUIHelper.AddTest();
|
||||
}
|
||||
UnityEngine.GUI.enabled = true;
|
||||
}
|
||||
|
||||
public override void PrintHeadPanel()
|
||||
{
|
||||
base.PrintHeadPanel();
|
||||
DrawFilters();
|
||||
}
|
||||
|
||||
protected override void RunTests(params UITestRunnerFilter[] filters)
|
||||
{
|
||||
if (EditorUtility.scriptCompilationFailed)
|
||||
{
|
||||
Debug.LogError("Fix compilation issues before running tests");
|
||||
return;
|
||||
}
|
||||
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
filter.ClearResults(newResultList.OfType<UITestRunnerFilter.IClearableResult>().ToList());
|
||||
}
|
||||
|
||||
RerunCallbackData.instance.runFilters = filters;
|
||||
RerunCallbackData.instance.testMode = TestMode.EditMode;
|
||||
|
||||
var testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
testRunnerApi.Execute(new ExecutionSettings()
|
||||
{
|
||||
filters = filters.Select(filter => new Filter()
|
||||
{
|
||||
assemblyNames = filter.assemblyNames,
|
||||
categoryNames = filter.categoryNames,
|
||||
groupNames = filter.groupNames,
|
||||
testMode = TestMode,
|
||||
testNames = filter.testNames
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
public override TestPlatform TestPlatform { get { return TestPlatform.EditMode; } }
|
||||
|
||||
protected override bool IsBusy()
|
||||
{
|
||||
return TestRunnerApi.IsRunActive() || EditorApplication.isCompiling || EditorApplication.isPlaying;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,241 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
[Serializable]
|
||||
internal class PlayModeTestListGUI : TestListGUI
|
||||
{
|
||||
private struct PlayerMenuItem
|
||||
{
|
||||
public GUIContent name;
|
||||
public bool filterSelectedTestsOnly;
|
||||
public bool buildOnly;
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private int m_SelectedOption;
|
||||
|
||||
public override TestMode TestMode
|
||||
{
|
||||
get { return TestMode.PlayMode; }
|
||||
}
|
||||
|
||||
private string GetBuildText()
|
||||
{
|
||||
switch (EditorUserBuildSettings.activeBuildTarget)
|
||||
{
|
||||
case BuildTarget.Android:
|
||||
if (EditorUserBuildSettings.exportAsGoogleAndroidProject)
|
||||
return "Export";
|
||||
break;
|
||||
case BuildTarget.iOS:
|
||||
return "Export";
|
||||
}
|
||||
return "Build";
|
||||
}
|
||||
|
||||
private string PickBuildLocation()
|
||||
{
|
||||
var target = EditorUserBuildSettings.activeBuildTarget;
|
||||
var targetGroup = BuildPipeline.GetBuildTargetGroup(target);
|
||||
var lastLocation = EditorUserBuildSettings.GetBuildLocation(target);
|
||||
var extension = PostprocessBuildPlayer.GetExtensionForBuildTarget(targetGroup, target, BuildOptions.None);
|
||||
var defaultName = FileUtil.GetLastPathNameComponent(lastLocation);
|
||||
lastLocation = string.IsNullOrEmpty(lastLocation) ? string.Empty : Path.GetDirectoryName(lastLocation);
|
||||
bool updateExistingBuild;
|
||||
var location = EditorUtility.SaveBuildPanel(target, $"{GetBuildText()} {target}", lastLocation, defaultName, extension,
|
||||
out updateExistingBuild);
|
||||
if (!string.IsNullOrEmpty(location))
|
||||
EditorUserBuildSettings.SetBuildLocation(target, location);
|
||||
return location;
|
||||
}
|
||||
|
||||
private void ExecuteAction(PlayerMenuItem item)
|
||||
{
|
||||
var runSettings = new PlayerLauncherTestRunSettings();
|
||||
runSettings.buildOnly = item.buildOnly;
|
||||
if (runSettings.buildOnly)
|
||||
{
|
||||
runSettings.buildOnlyLocationPath = PickBuildLocation();
|
||||
if (string.IsNullOrEmpty(runSettings.buildOnlyLocationPath))
|
||||
{
|
||||
Debug.LogWarning("Aborting, build selection was canceled.");
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (item.filterSelectedTestsOnly)
|
||||
RunTestsInPlayer(runSettings, SelectedTestsFilter);
|
||||
else
|
||||
{
|
||||
var filter = new UITestRunnerFilter { categoryNames = m_TestRunnerUIFilter.CategoryFilter };
|
||||
RunTestsInPlayer(runSettings, filter);
|
||||
}
|
||||
}
|
||||
|
||||
public override void PrintHeadPanel()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(false));
|
||||
base.PrintHeadPanel();
|
||||
|
||||
PlayerMenuItem[] menuItems;
|
||||
|
||||
if (EditorUserBuildSettings.installInBuildFolder)
|
||||
{
|
||||
menuItems = new []
|
||||
{
|
||||
// Note: We select here buildOnly = false, so build location dialog won't show up
|
||||
// The player won't actually be ran when using together with EditorUserBuildSettings.installInBuildFolder
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent("Install All Tests In Build Folder"), buildOnly = false, filterSelectedTestsOnly = false
|
||||
},
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent("Install Selected Tests In Build Folder"), buildOnly = false, filterSelectedTestsOnly = true
|
||||
}
|
||||
};
|
||||
}
|
||||
else
|
||||
{
|
||||
menuItems = new []
|
||||
{
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent("Run All Tests"), buildOnly = false, filterSelectedTestsOnly = false
|
||||
},
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent("Run Selected Tests"), buildOnly = false, filterSelectedTestsOnly = true
|
||||
},
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent($"{GetBuildText()} All Tests"), buildOnly = true, filterSelectedTestsOnly = false
|
||||
},
|
||||
new PlayerMenuItem()
|
||||
{
|
||||
name = new GUIContent($"{GetBuildText()} Selected Tests"), buildOnly = true, filterSelectedTestsOnly = true
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
m_SelectedOption = Math.Min(m_SelectedOption, menuItems.Length - 1);
|
||||
var selectedMenuItem = menuItems[m_SelectedOption];
|
||||
if (GUILayout.Button(
|
||||
new GUIContent($"{selectedMenuItem.name.text} ({EditorUserBuildSettings.activeBuildTarget})"),
|
||||
EditorStyles.toolbarButton))
|
||||
{
|
||||
ExecuteAction(selectedMenuItem);
|
||||
}
|
||||
|
||||
if (GUILayout.Button(GUIContent.none, EditorStyles.toolbarDropDown))
|
||||
{
|
||||
Vector2 mousePos = Event.current.mousePosition;
|
||||
EditorUtility.DisplayCustomMenu(new Rect(mousePos.x, mousePos.y, 0, 0),
|
||||
menuItems.Select(m => m.name).ToArray(),
|
||||
-1,
|
||||
(object userData, string[] options, int selected) => m_SelectedOption = selected,
|
||||
menuItems);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndHorizontal();
|
||||
DrawFilters();
|
||||
EditorGUILayout.BeginHorizontal(GUILayout.ExpandHeight(false));
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public override void RenderNoTestsInfo()
|
||||
{
|
||||
if (!TestListGUIHelper.SelectedFolderContainsTestAssembly())
|
||||
{
|
||||
var noTestText = "No tests to show";
|
||||
if (!PlayerSettings.playModeTestRunnerEnabled)
|
||||
{
|
||||
const string testsArePulledFromCustomAssemblues = "Test Assemblies are defined by Assembly Definitions that references the \"nunit.framework.dll\" Assembly Reference or the Assembly Definition Reference \"UnityEngine.TestRunner\".";
|
||||
const string infoTextAboutTestsInAllAssemblies =
|
||||
"To have tests in all assemblies enable it in the Test Runner window context menu";
|
||||
noTestText += Environment.NewLine + testsArePulledFromCustomAssemblues + Environment.NewLine +
|
||||
infoTextAboutTestsInAllAssemblies;
|
||||
}
|
||||
|
||||
EditorGUILayout.HelpBox(noTestText, MessageType.Info);
|
||||
if (GUILayout.Button("Create PlayMode Test Assembly Folder"))
|
||||
{
|
||||
TestListGUIHelper.AddFolderAndAsmDefForTesting();
|
||||
}
|
||||
}
|
||||
|
||||
if (!TestListGUIHelper.CanAddPlayModeTestScriptAndItWillCompile())
|
||||
{
|
||||
UnityEngine.GUI.enabled = false;
|
||||
EditorGUILayout.HelpBox("PlayMode test scripts can only be created in non editor test assemblies.", MessageType.Warning);
|
||||
}
|
||||
if (GUILayout.Button("Create Test Script in current folder"))
|
||||
{
|
||||
TestListGUIHelper.AddTest();
|
||||
}
|
||||
UnityEngine.GUI.enabled = true;
|
||||
}
|
||||
|
||||
protected override void RunTests(UITestRunnerFilter[] filters)
|
||||
{
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
filter.ClearResults(newResultList.OfType<UITestRunnerFilter.IClearableResult>().ToList());
|
||||
}
|
||||
|
||||
RerunCallbackData.instance.runFilters = filters;
|
||||
RerunCallbackData.instance.testMode = TestMode.PlayMode;
|
||||
|
||||
var testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
testRunnerApi.Execute(new ExecutionSettings()
|
||||
{
|
||||
filters = filters.Select(filter => new Filter()
|
||||
{
|
||||
assemblyNames = filter.assemblyNames,
|
||||
categoryNames = filter.categoryNames,
|
||||
groupNames = filter.groupNames,
|
||||
testMode = TestMode,
|
||||
testNames = filter.testNames
|
||||
}).ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
protected void RunTestsInPlayer(PlayerLauncherTestRunSettings runSettings, params UITestRunnerFilter[] filters)
|
||||
{
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
filter.ClearResults(newResultList.OfType<UITestRunnerFilter.IClearableResult>().ToList());
|
||||
}
|
||||
|
||||
var testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
|
||||
testRunnerApi.Execute(new ExecutionSettings()
|
||||
{
|
||||
overloadTestRunSettings = runSettings,
|
||||
filters = filters.Select(filter => new Filter()
|
||||
{
|
||||
assemblyNames = filter.assemblyNames,
|
||||
categoryNames = filter.categoryNames,
|
||||
groupNames = filter.groupNames,
|
||||
testMode = TestMode,
|
||||
testNames = filter.testNames
|
||||
}).ToArray(),
|
||||
targetPlatform = EditorUserBuildSettings.activeBuildTarget
|
||||
});
|
||||
}
|
||||
|
||||
public override TestPlatform TestPlatform { get { return TestPlatform.PlayMode; } }
|
||||
|
||||
protected override bool IsBusy()
|
||||
{
|
||||
return TestRunnerApi.IsRunActive() || PlaymodeLauncher.IsRunning || EditorApplication.isCompiling || EditorApplication.isPlaying;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,553 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text.RegularExpressions;
|
||||
using UnityEditor.IMGUI.Controls;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.GUI
|
||||
{
|
||||
internal abstract class TestListGUI
|
||||
{
|
||||
private static readonly GUIContent s_GUIRunSelectedTests = EditorGUIUtility.TrTextContent("Run Selected", "Run selected test(s)");
|
||||
private static readonly GUIContent s_GUIRunAllTests = EditorGUIUtility.TrTextContent("Run All", "Run all tests");
|
||||
private static readonly GUIContent s_GUIRerunFailedTests = EditorGUIUtility.TrTextContent("Rerun Failed", "Rerun all failed tests");
|
||||
private static readonly GUIContent s_GUIRun = EditorGUIUtility.TrTextContent("Run");
|
||||
private static readonly GUIContent s_GUIRunUntilFailed = EditorGUIUtility.TrTextContent("Run Until Failed");
|
||||
private static readonly GUIContent s_GUIRun100Times = EditorGUIUtility.TrTextContent("Run 100 times");
|
||||
private static readonly GUIContent s_GUIOpenTest = EditorGUIUtility.TrTextContent("Open source code");
|
||||
private static readonly GUIContent s_GUIOpenErrorLine = EditorGUIUtility.TrTextContent("Open error line");
|
||||
private static readonly GUIContent s_GUIClearResults = EditorGUIUtility.TrTextContent("Clear Results", "Clear all test results");
|
||||
|
||||
[SerializeField]
|
||||
protected TestRunnerWindow m_Window;
|
||||
|
||||
public List<TestRunnerResult> newResultList
|
||||
{
|
||||
get { return m_NewResultList; }
|
||||
set {
|
||||
m_ResultByKey = null;
|
||||
m_NewResultList = value;
|
||||
}
|
||||
}
|
||||
[SerializeField]
|
||||
private List<TestRunnerResult> m_NewResultList = new List<TestRunnerResult>();
|
||||
|
||||
Dictionary<string, TestRunnerResult> m_ResultByKey;
|
||||
internal Dictionary<string, TestRunnerResult> ResultsByKey
|
||||
{
|
||||
get
|
||||
{
|
||||
if (m_ResultByKey == null)
|
||||
m_ResultByKey = newResultList.ToDictionary(k => k.uniqueId);
|
||||
return m_ResultByKey;
|
||||
}
|
||||
}
|
||||
|
||||
[SerializeField]
|
||||
private string m_ResultText;
|
||||
[SerializeField]
|
||||
private string m_ResultStacktrace;
|
||||
|
||||
private TreeViewController m_TestListTree;
|
||||
[SerializeField]
|
||||
internal TreeViewState m_TestListState;
|
||||
[SerializeField]
|
||||
internal TestRunnerUIFilter m_TestRunnerUIFilter = new TestRunnerUIFilter();
|
||||
|
||||
private Vector2 m_TestInfoScroll, m_TestListScroll;
|
||||
private string m_PreviousProjectPath;
|
||||
private List<TestRunnerResult> m_QueuedResults = new List<TestRunnerResult>();
|
||||
|
||||
protected TestListGUI()
|
||||
{
|
||||
MonoCecilHelper = new MonoCecilHelper();
|
||||
AssetsDatabaseHelper = new AssetsDatabaseHelper();
|
||||
|
||||
GuiHelper = new GuiHelper(MonoCecilHelper, AssetsDatabaseHelper);
|
||||
}
|
||||
|
||||
protected IMonoCecilHelper MonoCecilHelper { get; private set; }
|
||||
protected IAssetsDatabaseHelper AssetsDatabaseHelper { get; private set; }
|
||||
protected IGuiHelper GuiHelper { get; private set; }
|
||||
protected UITestRunnerFilter[] SelectedTestsFilter => GetSelectedTestsAsFilter(m_TestListTree.GetSelection());
|
||||
|
||||
public abstract TestMode TestMode { get; }
|
||||
|
||||
public virtual void PrintHeadPanel()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
using (new EditorGUI.DisabledScope(IsBusy()))
|
||||
{
|
||||
if (GUILayout.Button(s_GUIRunAllTests, EditorStyles.toolbarButton))
|
||||
{
|
||||
var filter = new UITestRunnerFilter {categoryNames = m_TestRunnerUIFilter.CategoryFilter};
|
||||
RunTests(filter);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(m_TestListTree == null || !m_TestListTree.HasSelection() || IsBusy()))
|
||||
{
|
||||
if (GUILayout.Button(s_GUIRunSelectedTests, EditorStyles.toolbarButton))
|
||||
{
|
||||
RunTests(SelectedTestsFilter);
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(m_TestRunnerUIFilter.FailedCount == 0 || IsBusy()))
|
||||
{
|
||||
if (GUILayout.Button(s_GUIRerunFailedTests, EditorStyles.toolbarButton))
|
||||
{
|
||||
var failedTestnames = new List<string>();
|
||||
foreach (var result in newResultList)
|
||||
{
|
||||
if (result.isSuite)
|
||||
continue;
|
||||
if (result.resultStatus == TestRunnerResult.ResultStatus.Failed ||
|
||||
result.resultStatus == TestRunnerResult.ResultStatus.Inconclusive)
|
||||
failedTestnames.Add(result.fullName);
|
||||
}
|
||||
RunTests(new UITestRunnerFilter() {testNames = failedTestnames.ToArray(), categoryNames = m_TestRunnerUIFilter.CategoryFilter});
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
using (new EditorGUI.DisabledScope(IsBusy()))
|
||||
{
|
||||
if (GUILayout.Button(s_GUIClearResults, EditorStyles.toolbarButton))
|
||||
{
|
||||
foreach (var result in newResultList)
|
||||
{
|
||||
result.Clear();
|
||||
}
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList);
|
||||
Reload();
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
}
|
||||
GUILayout.FlexibleSpace();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
protected void DrawFilters()
|
||||
{
|
||||
EditorGUILayout.BeginHorizontal(EditorStyles.toolbar);
|
||||
m_TestRunnerUIFilter.Draw();
|
||||
EditorGUILayout.EndHorizontal();
|
||||
}
|
||||
|
||||
public bool HasTreeData()
|
||||
{
|
||||
return m_TestListTree != null;
|
||||
}
|
||||
|
||||
public virtual void RenderTestList()
|
||||
{
|
||||
if (m_TestListTree == null)
|
||||
{
|
||||
GUILayout.Label("Loading...");
|
||||
return;
|
||||
}
|
||||
|
||||
m_TestListScroll = EditorGUILayout.BeginScrollView(m_TestListScroll,
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MaxWidth(2000));
|
||||
|
||||
if (m_TestListTree.data.root == null || m_TestListTree.data.rowCount == 0 || (!m_TestListTree.isSearching && !m_TestListTree.data.GetItem(0).hasChildren))
|
||||
{
|
||||
if (m_TestRunnerUIFilter.IsFiltering)
|
||||
{
|
||||
if (GUILayout.Button("Clear filters"))
|
||||
{
|
||||
m_TestRunnerUIFilter.Clear();
|
||||
m_TestListTree.ReloadData();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
}
|
||||
RenderNoTestsInfo();
|
||||
}
|
||||
else
|
||||
{
|
||||
var treeRect = EditorGUILayout.GetControlRect(GUILayout.ExpandHeight(true), GUILayout.ExpandWidth(true));
|
||||
var treeViewKeyboardControlId = GUIUtility.GetControlID(FocusType.Keyboard);
|
||||
|
||||
m_TestListTree.OnGUI(treeRect, treeViewKeyboardControlId);
|
||||
}
|
||||
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
public virtual void RenderNoTestsInfo()
|
||||
{
|
||||
EditorGUILayout.HelpBox("No tests to show", MessageType.Info);
|
||||
}
|
||||
|
||||
public void RenderDetails()
|
||||
{
|
||||
m_TestInfoScroll = EditorGUILayout.BeginScrollView(m_TestInfoScroll);
|
||||
var resultTextSize = TestRunnerWindow.Styles.info.CalcSize(new GUIContent(m_ResultText));
|
||||
EditorGUILayout.SelectableLabel(m_ResultText, TestRunnerWindow.Styles.info,
|
||||
GUILayout.ExpandHeight(true),
|
||||
GUILayout.ExpandWidth(true),
|
||||
GUILayout.MinWidth(resultTextSize.x),
|
||||
GUILayout.MinHeight(resultTextSize.y));
|
||||
EditorGUILayout.EndScrollView();
|
||||
}
|
||||
|
||||
public void Reload()
|
||||
{
|
||||
if (m_TestListTree != null)
|
||||
{
|
||||
m_TestListTree.ReloadData();
|
||||
UpdateQueuedResults();
|
||||
}
|
||||
}
|
||||
|
||||
public void Repaint()
|
||||
{
|
||||
if (m_TestListTree == null || m_TestListTree.data.root == null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
m_TestListTree.Repaint();
|
||||
if (m_TestListTree.data.rowCount == 0)
|
||||
m_TestListTree.SetSelection(new int[0], false);
|
||||
TestSelectionCallback(m_TestListState.selectedIDs.ToArray());
|
||||
}
|
||||
|
||||
public void Init(TestRunnerWindow window, ITestAdaptor rootTest)
|
||||
{
|
||||
if (m_Window == null)
|
||||
{
|
||||
m_Window = window;
|
||||
}
|
||||
|
||||
if (m_TestListTree == null)
|
||||
{
|
||||
if (m_TestListState == null)
|
||||
{
|
||||
m_TestListState = new TreeViewState();
|
||||
}
|
||||
if (m_TestListTree == null)
|
||||
m_TestListTree = new TreeViewController(m_Window, m_TestListState);
|
||||
|
||||
m_TestListTree.deselectOnUnhandledMouseDown = false;
|
||||
|
||||
m_TestListTree.selectionChangedCallback += TestSelectionCallback;
|
||||
m_TestListTree.itemDoubleClickedCallback += TestDoubleClickCallback;
|
||||
m_TestListTree.contextClickItemCallback += TestContextClickCallback;
|
||||
|
||||
var testListTreeViewDataSource = new TestListTreeViewDataSource(m_TestListTree, this, rootTest);
|
||||
|
||||
if (!newResultList.Any())
|
||||
testListTreeViewDataSource.ExpandTreeOnCreation();
|
||||
|
||||
m_TestListTree.Init(new Rect(),
|
||||
testListTreeViewDataSource,
|
||||
new TestListTreeViewGUI(m_TestListTree),
|
||||
null);
|
||||
}
|
||||
|
||||
EditorApplication.update += RepaintIfProjectPathChanged;
|
||||
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList);
|
||||
m_TestRunnerUIFilter.RebuildTestList = () => m_TestListTree.ReloadData();
|
||||
m_TestRunnerUIFilter.SearchStringChanged = s => m_TestListTree.searchString = s;
|
||||
m_TestRunnerUIFilter.SearchStringCleared = () => FrameSelection();
|
||||
}
|
||||
|
||||
public void UpdateResult(TestRunnerResult result)
|
||||
{
|
||||
if (!HasTreeData())
|
||||
{
|
||||
m_QueuedResults.Add(result);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ResultsByKey.TryGetValue(result.uniqueId, out var testRunnerResult))
|
||||
{
|
||||
testRunnerResult.Update(result);
|
||||
Repaint();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public void UpdateTestTree(ITestAdaptor test)
|
||||
{
|
||||
if (!HasTreeData())
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
(m_TestListTree.data as TestListTreeViewDataSource).UpdateRootTest(test);
|
||||
|
||||
m_TestListTree.ReloadData();
|
||||
Repaint();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
|
||||
private void UpdateQueuedResults()
|
||||
{
|
||||
foreach (var testRunnerResult in m_QueuedResults)
|
||||
{
|
||||
if (ResultsByKey.TryGetValue(testRunnerResult.uniqueId, out var existingResult))
|
||||
{
|
||||
existingResult.Update(testRunnerResult);
|
||||
}
|
||||
}
|
||||
m_QueuedResults.Clear();
|
||||
TestSelectionCallback(m_TestListState.selectedIDs.ToArray());
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList);
|
||||
Repaint();
|
||||
m_Window.Repaint();
|
||||
}
|
||||
|
||||
internal void TestSelectionCallback(int[] selected)
|
||||
{
|
||||
if (m_TestListTree != null && selected.Length == 1)
|
||||
{
|
||||
if (m_TestListTree != null)
|
||||
{
|
||||
var node = m_TestListTree.FindItem(selected[0]);
|
||||
if (node is TestTreeViewItem)
|
||||
{
|
||||
var test = node as TestTreeViewItem;
|
||||
m_ResultText = test.GetResultText();
|
||||
m_ResultStacktrace = test.result.stacktrace;
|
||||
}
|
||||
}
|
||||
}
|
||||
else if (selected.Length == 0)
|
||||
{
|
||||
m_ResultText = "";
|
||||
}
|
||||
}
|
||||
|
||||
protected virtual void TestDoubleClickCallback(int id)
|
||||
{
|
||||
if (IsBusy())
|
||||
return;
|
||||
|
||||
RunTests(GetSelectedTestsAsFilter(new List<int> { id }));
|
||||
GUIUtility.ExitGUI();
|
||||
}
|
||||
|
||||
protected virtual void RunTests(params UITestRunnerFilter[] filters)
|
||||
{
|
||||
throw new NotImplementedException();
|
||||
}
|
||||
|
||||
protected virtual void TestContextClickCallback(int id)
|
||||
{
|
||||
if (id == 0)
|
||||
return;
|
||||
|
||||
var m = new GenericMenu();
|
||||
var testFilters = GetSelectedTestsAsFilter(m_TestListState.selectedIDs);
|
||||
var multilineSelection = m_TestListState.selectedIDs.Count > 1;
|
||||
|
||||
if (!multilineSelection)
|
||||
{
|
||||
var testNode = GetSelectedTest();
|
||||
var isNotSuite = !testNode.IsGroupNode;
|
||||
if (isNotSuite)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(m_ResultStacktrace))
|
||||
{
|
||||
m.AddItem(s_GUIOpenErrorLine,
|
||||
false,
|
||||
data =>
|
||||
{
|
||||
if (!GuiHelper.OpenScriptInExternalEditor(m_ResultStacktrace))
|
||||
{
|
||||
GuiHelper.OpenScriptInExternalEditor(testNode.type, testNode.method);
|
||||
}
|
||||
},
|
||||
"");
|
||||
}
|
||||
|
||||
m.AddItem(s_GUIOpenTest,
|
||||
false,
|
||||
data => GuiHelper.OpenScriptInExternalEditor(testNode.type, testNode.method),
|
||||
"");
|
||||
m.AddSeparator("");
|
||||
}
|
||||
}
|
||||
|
||||
if (!IsBusy())
|
||||
{
|
||||
m.AddItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRun,
|
||||
false,
|
||||
data => RunTests(testFilters),
|
||||
"");
|
||||
|
||||
if (EditorPrefs.GetBool("DeveloperMode", false))
|
||||
{
|
||||
m.AddItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRunUntilFailed,
|
||||
false,
|
||||
data =>
|
||||
{
|
||||
foreach (var filter in testFilters)
|
||||
{
|
||||
filter.testRepetitions = int.MaxValue;
|
||||
}
|
||||
|
||||
RunTests(testFilters);
|
||||
},
|
||||
"");
|
||||
|
||||
m.AddItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRun100Times,
|
||||
false,
|
||||
data =>
|
||||
{
|
||||
foreach (var filter in testFilters)
|
||||
{
|
||||
filter.testRepetitions = 100;
|
||||
}
|
||||
|
||||
RunTests(testFilters);
|
||||
},
|
||||
"");
|
||||
}
|
||||
}
|
||||
else
|
||||
m.AddDisabledItem(multilineSelection ? s_GUIRunSelectedTests : s_GUIRun, false);
|
||||
|
||||
m.ShowAsContext();
|
||||
}
|
||||
|
||||
private UITestRunnerFilter[] GetSelectedTestsAsFilter(IEnumerable<int> selectedIDs)
|
||||
{
|
||||
var namesToRun = new List<string>();
|
||||
var assembliesForNamesToRun = new List<string>();
|
||||
var exactNamesToRun = new List<string>();
|
||||
var assembliesToRun = new List<string>();
|
||||
foreach (var lineId in selectedIDs)
|
||||
{
|
||||
var line = m_TestListTree.FindItem(lineId);
|
||||
if (line is TestTreeViewItem)
|
||||
{
|
||||
var testLine = line as TestTreeViewItem;
|
||||
if (testLine.IsGroupNode && !testLine.FullName.Contains("+"))
|
||||
{
|
||||
if (testLine.parent != null && testLine.parent.displayName == "Invisible Root Item")
|
||||
{
|
||||
//Root node selected. Use an empty TestRunnerFilter to run every test
|
||||
return new[] {new UITestRunnerFilter()};
|
||||
}
|
||||
|
||||
if (testLine.FullName.EndsWith(".dll", StringComparison.OrdinalIgnoreCase))
|
||||
{
|
||||
assembliesToRun.Add(UITestRunnerFilter.AssemblyNameFromPath(testLine.FullName));
|
||||
}
|
||||
else
|
||||
{
|
||||
namesToRun.Add($"^{Regex.Escape(testLine.FullName)}$");
|
||||
var assembly = UITestRunnerFilter.AssemblyNameFromPath(testLine.GetAssemblyName());
|
||||
if (!string.IsNullOrEmpty(assembly) && !assembliesForNamesToRun.Contains(assembly))
|
||||
{
|
||||
assembliesForNamesToRun.Add(assembly);
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
exactNamesToRun.Add(testLine.FullName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var filters = new List<UITestRunnerFilter>();
|
||||
|
||||
if (assembliesToRun.Count > 0)
|
||||
{
|
||||
filters.Add(new UITestRunnerFilter()
|
||||
{
|
||||
assemblyNames = assembliesToRun.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
if (namesToRun.Count > 0)
|
||||
{
|
||||
filters.Add(new UITestRunnerFilter()
|
||||
{
|
||||
groupNames = namesToRun.ToArray(),
|
||||
assemblyNames = assembliesForNamesToRun.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
if (exactNamesToRun.Count > 0)
|
||||
{
|
||||
filters.Add(new UITestRunnerFilter()
|
||||
{
|
||||
testNames = exactNamesToRun.ToArray()
|
||||
});
|
||||
}
|
||||
|
||||
if (filters.Count == 0)
|
||||
{
|
||||
filters.Add(new UITestRunnerFilter());
|
||||
}
|
||||
|
||||
var categories = m_TestRunnerUIFilter.CategoryFilter.ToArray();
|
||||
if (categories.Length > 0)
|
||||
{
|
||||
foreach (var filter in filters)
|
||||
{
|
||||
filter.categoryNames = categories;
|
||||
}
|
||||
}
|
||||
|
||||
return filters.ToArray();
|
||||
}
|
||||
|
||||
private TestTreeViewItem GetSelectedTest()
|
||||
{
|
||||
foreach (var lineId in m_TestListState.selectedIDs)
|
||||
{
|
||||
var line = m_TestListTree.FindItem(lineId);
|
||||
if (line is TestTreeViewItem)
|
||||
{
|
||||
return line as TestTreeViewItem;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private void FrameSelection()
|
||||
{
|
||||
if (m_TestListTree.HasSelection())
|
||||
{
|
||||
var firstClickedID = m_TestListState.selectedIDs.First<int>() == m_TestListState.lastClickedID ? m_TestListState.selectedIDs.Last<int>() : m_TestListState.selectedIDs.First<int>();
|
||||
m_TestListTree.Frame(firstClickedID, true, false);
|
||||
}
|
||||
}
|
||||
|
||||
public abstract TestPlatform TestPlatform { get; }
|
||||
|
||||
public void RebuildUIFilter()
|
||||
{
|
||||
m_TestRunnerUIFilter.UpdateCounters(newResultList);
|
||||
if (m_TestRunnerUIFilter.IsFiltering)
|
||||
{
|
||||
m_TestListTree.ReloadData();
|
||||
}
|
||||
}
|
||||
|
||||
public void RepaintIfProjectPathChanged()
|
||||
{
|
||||
var path = TestListGUIHelper.GetActiveFolderPath();
|
||||
if (path != m_PreviousProjectPath)
|
||||
{
|
||||
m_PreviousProjectPath = path;
|
||||
TestRunnerWindow.s_Instance.Repaint();
|
||||
}
|
||||
|
||||
EditorApplication.update -= RepaintIfProjectPathChanged;
|
||||
}
|
||||
|
||||
protected abstract bool IsBusy();
|
||||
}
|
||||
}
|
@@ -0,0 +1,63 @@
|
||||
using System;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal;
|
||||
|
||||
namespace UnityEditor.TestTools
|
||||
{
|
||||
/// <summary>
|
||||
/// Ignore attributes dedicated to Asset Import Pipeline backend version handling.
|
||||
/// </summary>
|
||||
internal static class AssetPipelineIgnore
|
||||
{
|
||||
internal enum AssetPipelineBackend
|
||||
{
|
||||
V1,
|
||||
V2
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ignore the test when running with the legacy Asset Import Pipeline V1 backend.
|
||||
/// </summary>
|
||||
internal class IgnoreInV1 : AssetPipelineIgnoreAttribute
|
||||
{
|
||||
public IgnoreInV1(string ignoreReason) : base(AssetPipelineBackend.V1, ignoreReason) {}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ignore the test when running with the latest Asset Import Pipeline V2 backend.
|
||||
/// </summary>
|
||||
internal class IgnoreInV2 : AssetPipelineIgnoreAttribute
|
||||
{
|
||||
public IgnoreInV2(string ignoreReason) : base(AssetPipelineBackend.V2, ignoreReason) {}
|
||||
}
|
||||
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||
internal class AssetPipelineIgnoreAttribute : NUnitAttribute, IApplyToTest
|
||||
{
|
||||
readonly string m_IgnoreReason;
|
||||
readonly AssetPipelineBackend m_IgnoredBackend;
|
||||
static readonly AssetPipelineBackend k_ActiveBackend = AssetDatabase.IsV2Enabled()
|
||||
? AssetPipelineBackend.V2
|
||||
: AssetPipelineBackend.V1;
|
||||
|
||||
static string ActiveBackendName = Enum.GetName(typeof(AssetPipelineBackend), k_ActiveBackend);
|
||||
|
||||
public AssetPipelineIgnoreAttribute(AssetPipelineBackend backend, string ignoreReason)
|
||||
{
|
||||
m_IgnoredBackend = backend;
|
||||
m_IgnoreReason = ignoreReason;
|
||||
}
|
||||
|
||||
public void ApplyToTest(Test test)
|
||||
{
|
||||
if (k_ActiveBackend == m_IgnoredBackend)
|
||||
{
|
||||
test.RunState = RunState.Ignored;
|
||||
var skipReason = string.Format("Not supported by asset pipeline {0} backend {1}", ActiveBackendName, m_IgnoreReason);
|
||||
test.Properties.Add(PropertyNames.SkipReason, skipReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,15 @@
|
||||
namespace UnityEditor.TestTools
|
||||
{
|
||||
/// <summary>
|
||||
/// An interface for a callback modifying the <see cref="BuildPlayerOptions"/> when building a player for running tests in the runtime.
|
||||
/// </summary>
|
||||
public interface ITestPlayerBuildModifier
|
||||
{
|
||||
/// <summary>
|
||||
/// A callback to modify the <see cref="BuildPlayerOptions"/> when building a player for test run. Return the modified version of the provided build options.
|
||||
/// </summary>
|
||||
/// <param name="playerOptions">The unmodified BuildPlayerOptions.</param>
|
||||
/// <returns>The modified BuildPlayerOptions.</returns>
|
||||
BuildPlayerOptions ModifyOptions(BuildPlayerOptions playerOptions);
|
||||
}
|
||||
}
|
@@ -0,0 +1,33 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools
|
||||
{
|
||||
/// <summary>
|
||||
/// The `TestPlayerBuildModifierAttribute` attribute can be applied to test assemblies (will affect every test in the assembly).
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly)]
|
||||
public class TestPlayerBuildModifierAttribute : Attribute
|
||||
{
|
||||
private Type m_Type;
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of TestPlayerBuildModifierAttribute or throws an <see cref="ArgumentException"/>.
|
||||
/// </summary>
|
||||
/// <param name="type"></param>
|
||||
/// <exception cref="ArgumentException">Throws a <see cref="ArgumentException"/> if the type provided does not implemented the `ITestPlayerBuildModifier` interface. </exception>
|
||||
public TestPlayerBuildModifierAttribute(Type type)
|
||||
{
|
||||
var interfaceType = typeof(ITestPlayerBuildModifier);
|
||||
if (!interfaceType.IsAssignableFrom(type))
|
||||
{
|
||||
throw new ArgumentException(string.Format("Type provided to {0} does not implement {1}", this.GetType().Name, interfaceType.Name));
|
||||
}
|
||||
m_Type = type;
|
||||
}
|
||||
|
||||
internal ITestPlayerBuildModifier ConstructModifier()
|
||||
{
|
||||
return Activator.CreateInstance(m_Type) as ITestPlayerBuildModifier;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -0,0 +1,162 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestRunner.NUnitExtensions.Runner;
|
||||
using UnityEngine.TestTools.NUnitExtensions;
|
||||
using UnityEngine.TestTools.Logging;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestRunnerStateSerializer : IStateSerializer
|
||||
{
|
||||
private const BindingFlags Flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.FlattenHierarchy;
|
||||
|
||||
[SerializeField]
|
||||
private HideFlags m_OriginalHideFlags;
|
||||
|
||||
[SerializeField]
|
||||
private bool m_ShouldRestore;
|
||||
|
||||
[SerializeField]
|
||||
private string m_TestObjectTypeName;
|
||||
|
||||
[SerializeField]
|
||||
private ScriptableObject m_TestObject;
|
||||
|
||||
[SerializeField]
|
||||
private string m_TestObjectTxt;
|
||||
|
||||
[SerializeField]
|
||||
private long StartTicks;
|
||||
|
||||
[SerializeField]
|
||||
private double StartTimeOA;
|
||||
|
||||
[SerializeField]
|
||||
private string output;
|
||||
|
||||
[SerializeField]
|
||||
private LogMatch[] m_ExpectedLogs;
|
||||
|
||||
public bool ShouldRestore()
|
||||
{
|
||||
return m_ShouldRestore;
|
||||
}
|
||||
|
||||
public void SaveContext()
|
||||
{
|
||||
var currentContext = UnityTestExecutionContext.CurrentContext;
|
||||
|
||||
if (currentContext.TestObject != null)
|
||||
{
|
||||
m_TestObjectTypeName = currentContext.TestObject.GetType().AssemblyQualifiedName;
|
||||
m_TestObject = null;
|
||||
m_TestObjectTxt = null;
|
||||
if (currentContext.TestObject is ScriptableObject)
|
||||
{
|
||||
m_TestObject = currentContext.TestObject as ScriptableObject;
|
||||
m_OriginalHideFlags = m_TestObject.hideFlags;
|
||||
m_TestObject.hideFlags |= HideFlags.DontSave;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_TestObjectTxt = JsonUtility.ToJson(currentContext.TestObject);
|
||||
}
|
||||
}
|
||||
|
||||
output = currentContext.CurrentResult.Output;
|
||||
StartTicks = currentContext.StartTicks;
|
||||
StartTimeOA = currentContext.StartTime.ToOADate();
|
||||
if (LogScope.HasCurrentLogScope())
|
||||
{
|
||||
m_ExpectedLogs = LogScope.Current.ExpectedLogs.ToArray();
|
||||
}
|
||||
|
||||
m_ShouldRestore = true;
|
||||
}
|
||||
|
||||
public void RestoreContext()
|
||||
{
|
||||
var currentContext = UnityTestExecutionContext.CurrentContext;
|
||||
|
||||
var outputProp = currentContext.CurrentResult.GetType().BaseType.GetField("_output", Flags);
|
||||
(outputProp.GetValue(currentContext.CurrentResult) as StringBuilder).Append(output);
|
||||
|
||||
currentContext.StartTicks = StartTicks;
|
||||
currentContext.StartTime = DateTime.FromOADate(StartTimeOA);
|
||||
if (LogScope.HasCurrentLogScope())
|
||||
{
|
||||
LogScope.Current.ExpectedLogs = new Queue<LogMatch>(m_ExpectedLogs);
|
||||
}
|
||||
|
||||
m_ShouldRestore = false;
|
||||
}
|
||||
|
||||
public bool CanRestoreFromScriptableObject(Type requestedType)
|
||||
{
|
||||
if (m_TestObject == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_TestObjectTypeName == requestedType.AssemblyQualifiedName;
|
||||
}
|
||||
|
||||
public ScriptableObject RestoreScriptableObjectInstance()
|
||||
{
|
||||
if (m_TestObject == null)
|
||||
{
|
||||
Debug.LogError("No object to restore");
|
||||
return null;
|
||||
}
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
var temp = m_TestObject;
|
||||
m_TestObject = null;
|
||||
m_TestObjectTypeName = null;
|
||||
return temp;
|
||||
}
|
||||
|
||||
public bool CanRestoreFromJson(Type requestedType)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_TestObjectTxt))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return m_TestObjectTypeName == requestedType.AssemblyQualifiedName;
|
||||
}
|
||||
|
||||
public void RestoreClassFromJson(ref object instance)
|
||||
{
|
||||
if (string.IsNullOrEmpty(m_TestObjectTxt))
|
||||
{
|
||||
Debug.LogWarning("No JSON representation to restore");
|
||||
return;
|
||||
}
|
||||
JsonUtility.FromJsonOverwrite(m_TestObjectTxt, instance);
|
||||
m_TestObjectTxt = null;
|
||||
m_TestObjectTypeName = null;
|
||||
}
|
||||
|
||||
private void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (m_TestObject == null)
|
||||
{
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
return;
|
||||
}
|
||||
|
||||
//We set the DontSave flag here because the ScriptableObject would be nulled right before entering EditMode
|
||||
if (state == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
m_TestObject.hideFlags |= HideFlags.DontSave;
|
||||
}
|
||||
else if (state == PlayModeStateChange.EnteredEditMode)
|
||||
{
|
||||
m_TestObject.hideFlags = m_OriginalHideFlags;
|
||||
EditorApplication.playModeStateChanged -= OnPlayModeStateChanged;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,32 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal;
|
||||
|
||||
namespace UnityEditor.TestTools
|
||||
{
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||
internal class RequireApiProfileAttribute : NUnitAttribute, IApplyToTest
|
||||
{
|
||||
public ApiCompatibilityLevel[] apiProfiles { get; private set; }
|
||||
|
||||
public RequireApiProfileAttribute(params ApiCompatibilityLevel[] apiProfiles)
|
||||
{
|
||||
this.apiProfiles = apiProfiles;
|
||||
}
|
||||
|
||||
void IApplyToTest.ApplyToTest(Test test)
|
||||
{
|
||||
test.Properties.Add(PropertyNames.Category, string.Format("ApiProfile({0})", string.Join(", ", apiProfiles.Select(p => p.ToString()).OrderBy(p => p).ToArray())));
|
||||
ApiCompatibilityLevel testProfile = PlayerSettings.GetApiCompatibilityLevel(EditorUserBuildSettings.activeBuildTargetGroup);
|
||||
|
||||
if (!apiProfiles.Contains(testProfile))
|
||||
{
|
||||
string skipReason = "Skipping test as it requires a compatible api profile set: " + string.Join(", ", apiProfiles.Select(p => p.ToString()).ToArray());
|
||||
test.RunState = RunState.Skipped;
|
||||
test.Properties.Add(PropertyNames.SkipReason, skipReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,48 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal;
|
||||
|
||||
namespace UnityEditor.TestTools
|
||||
{
|
||||
/// <summary>
|
||||
/// The `RequirePlatformSupportAttribute` attribute can be applied to test assemblies (will affect every test in the assembly), fixtures (will
|
||||
/// affect every test in the fixture), or to individual test methods.
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Assembly | AttributeTargets.Class | AttributeTargets.Method)]
|
||||
public class RequirePlatformSupportAttribute : NUnitAttribute, IApplyToTest
|
||||
{
|
||||
/// <summary>
|
||||
/// Initializes and returns an instance of RequirePlatformSupportAttribute.
|
||||
/// </summary>
|
||||
/// <param name="platforms">The <see cref="BuildTarget"/> platform to run the test on.</param>
|
||||
public RequirePlatformSupportAttribute(params BuildTarget[] platforms)
|
||||
{
|
||||
this.platforms = platforms;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// The build target platform, see [BuildTarget](https://docs.unity3d.com/ScriptReference/BuildTarget.html).
|
||||
/// </summary>
|
||||
public BuildTarget[] platforms { get; private set; }
|
||||
|
||||
/// <summary>
|
||||
/// Modifies a test as defined for the specific attribute.
|
||||
/// </summary>
|
||||
/// <param name="test">The test to modify</param>
|
||||
void IApplyToTest.ApplyToTest(Test test)
|
||||
{
|
||||
test.Properties.Add(PropertyNames.Category, string.Format("RequirePlatformSupport({0})", string.Join(", ", platforms.Select(p => p.ToString()).OrderBy(p => p).ToArray())));
|
||||
|
||||
if (!platforms.All(p => BuildPipeline.IsBuildTargetSupported(BuildTargetGroup.Unknown, p)))
|
||||
{
|
||||
var missingPlatforms = platforms.Where(p => !BuildPipeline.IsBuildTargetSupported(BuildTargetGroup.Unknown, p)).Select(p => p.ToString()).ToArray();
|
||||
string skipReason = "Test cannot be run as it requires support for the following platforms to be installed: " + string.Join(", ", missingPlatforms);
|
||||
|
||||
test.RunState = RunState.Skipped;
|
||||
test.Properties.Add(PropertyNames.SkipReason, skipReason);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System.Linq;
|
||||
using UnityEditor.Build;
|
||||
|
||||
namespace UnityEditor.TestRunner
|
||||
{
|
||||
// This class is invoked from native, during build
|
||||
internal class TestBuildAssemblyFilter : IFilterBuildAssemblies
|
||||
{
|
||||
private const string nunitAssemblyName = "nunit.framework";
|
||||
private const string unityTestRunnerAssemblyName = "UnityEngine.TestRunner";
|
||||
|
||||
public int callbackOrder { get; }
|
||||
public string[] OnFilterAssemblies(BuildOptions buildOptions, string[] assemblies)
|
||||
{
|
||||
if ((buildOptions & BuildOptions.IncludeTestAssemblies) == BuildOptions.IncludeTestAssemblies || PlayerSettings.playModeTestRunnerEnabled)
|
||||
{
|
||||
return assemblies;
|
||||
}
|
||||
return assemblies.Where(x => !x.Contains(nunitAssemblyName) && !x.Contains(unityTestRunnerAssemblyName)).ToArray();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,109 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal abstract class AttributeFinderBase : IAttributeFinder
|
||||
{
|
||||
public abstract IEnumerable<Type> Search(ITest tests, ITestFilter filter, RuntimePlatform testTargetPlatform);
|
||||
}
|
||||
|
||||
internal interface IAttributeFinder
|
||||
{
|
||||
IEnumerable<Type> Search(ITest tests, ITestFilter filter, RuntimePlatform testTargetPlatform);
|
||||
}
|
||||
|
||||
internal abstract class AttributeFinderBase<T1, T2> : AttributeFinderBase where T2 : Attribute
|
||||
{
|
||||
private readonly Func<T2, Type> m_TypeSelector;
|
||||
protected AttributeFinderBase(Func<T2, Type> typeSelector)
|
||||
{
|
||||
m_TypeSelector = typeSelector;
|
||||
}
|
||||
|
||||
public override IEnumerable<Type> Search(ITest tests, ITestFilter filter, RuntimePlatform testTargetPlatform)
|
||||
{
|
||||
var selectedTests = new List<ITest>();
|
||||
GetMatchingTests(tests, filter, ref selectedTests, testTargetPlatform);
|
||||
|
||||
var result = new List<Type>();
|
||||
result.AddRange(GetTypesFromPrebuildAttributes(selectedTests));
|
||||
result.AddRange(GetTypesFromInterface(selectedTests, testTargetPlatform));
|
||||
|
||||
return result.Distinct();
|
||||
}
|
||||
|
||||
private static void GetMatchingTests(ITest tests, ITestFilter filter, ref List<ITest> resultList, RuntimePlatform testTargetPlatform)
|
||||
{
|
||||
foreach (var test in tests.Tests)
|
||||
{
|
||||
if (IsTestEnabledOnPlatform(test, testTargetPlatform))
|
||||
{
|
||||
if (test.IsSuite)
|
||||
{
|
||||
GetMatchingTests(test, filter, ref resultList, testTargetPlatform);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (filter.Pass(test))
|
||||
resultList.Add(test);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static bool IsTestEnabledOnPlatform(ITest test, RuntimePlatform testTargetPlatform)
|
||||
{
|
||||
if (test.Method == null)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
var attributesFromMethods = test.Method.GetCustomAttributes<UnityPlatformAttribute>(true);
|
||||
var attributesFromTypes = test.Method.TypeInfo.GetCustomAttributes<UnityPlatformAttribute>(true);
|
||||
|
||||
if (attributesFromMethods.Length == 0 && attributesFromTypes.Length == 0)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!attributesFromMethods.All(a => a.IsPlatformSupported(testTargetPlatform)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!attributesFromTypes.All(a => a.IsPlatformSupported(testTargetPlatform)))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private IEnumerable<Type> GetTypesFromPrebuildAttributes(IEnumerable<ITest> tests)
|
||||
{
|
||||
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies();
|
||||
allAssemblies = allAssemblies.Where(x => x.GetReferencedAssemblies().Any(z => z.Name == "UnityEditor.TestRunner")).ToArray();
|
||||
var attributesFromAssemblies = allAssemblies.SelectMany(assembly => assembly.GetCustomAttributes(typeof(T2), true).OfType<T2>());
|
||||
var attributesFromMethods = tests.SelectMany(t => t.Method.GetCustomAttributes<T2>(true).Select(attribute => attribute));
|
||||
var attributesFromTypes = tests.SelectMany(t => t.Method.TypeInfo.GetCustomAttributes<T2>(true).Select(attribute => attribute));
|
||||
|
||||
var result = new List<T2>();
|
||||
result.AddRange(attributesFromAssemblies);
|
||||
result.AddRange(attributesFromMethods);
|
||||
result.AddRange(attributesFromTypes);
|
||||
|
||||
return result.Select(m_TypeSelector).Where(type => type != null);
|
||||
}
|
||||
|
||||
private static IEnumerable<Type> GetTypesFromInterface(IEnumerable<ITest> selectedTests, RuntimePlatform testTargetPlatform)
|
||||
{
|
||||
var typesWithInterfaces = selectedTests.Where(t => typeof(T1).IsAssignableFrom(t.Method.TypeInfo.Type) && IsTestEnabledOnPlatform(t, testTargetPlatform));
|
||||
return typesWithInterfaces.Select(t => t.Method.TypeInfo.Type);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,44 @@
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class DelayedCallback
|
||||
{
|
||||
private System.Action m_Callback;
|
||||
private double m_CallbackTime;
|
||||
private double m_Delay;
|
||||
|
||||
public DelayedCallback(System.Action function, double timeFromNow)
|
||||
{
|
||||
m_Callback = function;
|
||||
m_CallbackTime = EditorApplication.timeSinceStartup + timeFromNow;
|
||||
m_Delay = timeFromNow;
|
||||
EditorApplication.update += Update;
|
||||
}
|
||||
|
||||
public void Clear()
|
||||
{
|
||||
EditorApplication.update -= Update;
|
||||
m_CallbackTime = 0.0;
|
||||
m_Callback = null;
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (EditorApplication.timeSinceStartup > m_CallbackTime)
|
||||
{
|
||||
// Clear state before firing callback to ensure reset (callback could call ExitGUI)
|
||||
var callback = m_Callback;
|
||||
Clear();
|
||||
|
||||
callback?.Invoke();
|
||||
}
|
||||
}
|
||||
|
||||
public void Reset()
|
||||
{
|
||||
if (m_Callback != null)
|
||||
{
|
||||
m_CallbackTime = EditorApplication.timeSinceStartup + m_Delay;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,161 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEditor.TestTools.TestRunner.GUI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestRunner.Utils;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class EditModeLauncher : TestLauncherBase
|
||||
{
|
||||
public static bool IsRunning;
|
||||
internal readonly EditModeRunner m_EditModeRunner;
|
||||
public bool launchedOutsideApi;
|
||||
|
||||
// provided for backward compatibility with Rider UnitTesting prior to Rider package v.1.1.1
|
||||
public EditModeLauncher(UITestRunnerFilter filter, TestPlatform platform)
|
||||
{
|
||||
launchedOutsideApi = true;
|
||||
var apiFilter = new[]
|
||||
{
|
||||
new Filter()
|
||||
{
|
||||
testMode = TestMode.EditMode,
|
||||
testNames = filter.testNames,
|
||||
categoryNames = filter.categoryNames,
|
||||
groupNames = filter.groupNames,
|
||||
assemblyNames = filter.assemblyNames
|
||||
}
|
||||
};
|
||||
|
||||
ScriptableObject.CreateInstance<TestRunnerApi>().Execute(new ExecutionSettings(apiFilter));
|
||||
}
|
||||
|
||||
public EditModeLauncher(Filter[] filters, TestPlatform platform, bool runSynchronously)
|
||||
{
|
||||
TestEnumerator.Reset();
|
||||
m_EditModeRunner = ScriptableObject.CreateInstance<EditModeRunner>();
|
||||
m_EditModeRunner.UnityTestAssemblyRunnerFactory = new UnityTestAssemblyRunnerFactory();
|
||||
m_EditModeRunner.Init(filters, platform, runSynchronously);
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
if (launchedOutsideApi)
|
||||
{
|
||||
// Do not use the launcher, as it will be relaunched trough the api. See ctor.
|
||||
return;
|
||||
}
|
||||
|
||||
IsRunning = true;
|
||||
|
||||
SceneSetup[] previousSceneSetup;
|
||||
if (!OpenNewScene(out previousSceneSetup))
|
||||
return;
|
||||
|
||||
var callback = AddEventHandler<EditModeRunnerCallback>();
|
||||
callback.previousSceneSetup = previousSceneSetup;
|
||||
callback.runner = m_EditModeRunner;
|
||||
AddEventHandler<CallbacksDelegatorListener>();
|
||||
|
||||
m_EditModeRunner.Run();
|
||||
AddEventHandler<BackgroundListener>();
|
||||
AddEventHandler<TestRunCallbackListener>();
|
||||
|
||||
if (m_EditModeRunner.RunningSynchronously)
|
||||
m_EditModeRunner.CompleteSynchronously();
|
||||
}
|
||||
|
||||
private static bool OpenNewScene(out SceneSetup[] previousSceneSetup)
|
||||
{
|
||||
previousSceneSetup = null;
|
||||
|
||||
var sceneCount = SceneManager.sceneCount;
|
||||
|
||||
var scene = SceneManager.GetSceneAt(0);
|
||||
var isSceneNotPersisted = string.IsNullOrEmpty(scene.path);
|
||||
|
||||
if (sceneCount == 1 && isSceneNotPersisted)
|
||||
{
|
||||
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects, NewSceneMode.Single);
|
||||
return true;
|
||||
}
|
||||
RemoveUntitledScenes();
|
||||
|
||||
// In case the user chose not to save the dirty scenes we reload them
|
||||
ReloadUnsavedDirtyScene();
|
||||
|
||||
previousSceneSetup = EditorSceneManager.GetSceneManagerSetup();
|
||||
|
||||
scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Additive);
|
||||
SceneManager.SetActiveScene(scene);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static void ReloadUnsavedDirtyScene()
|
||||
{
|
||||
for (var i = 0; i < SceneManager.sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
var isSceneNotPersisted = string.IsNullOrEmpty(scene.path);
|
||||
var isSceneDirty = scene.isDirty;
|
||||
if (isSceneNotPersisted && isSceneDirty)
|
||||
{
|
||||
EditorSceneManager.ReloadScene(scene);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void RemoveUntitledScenes()
|
||||
{
|
||||
int sceneCount = SceneManager.sceneCount;
|
||||
|
||||
var scenesToClose = new List<Scene>();
|
||||
for (var i = 0; i < sceneCount; i++)
|
||||
{
|
||||
var scene = SceneManager.GetSceneAt(i);
|
||||
var isSceneNotPersisted = string.IsNullOrEmpty(scene.path);
|
||||
if (isSceneNotPersisted)
|
||||
{
|
||||
scenesToClose.Add(scene);
|
||||
}
|
||||
}
|
||||
foreach (Scene scene in scenesToClose)
|
||||
{
|
||||
EditorSceneManager.CloseScene(scene, true);
|
||||
}
|
||||
}
|
||||
|
||||
public class BackgroundListener : ScriptableObject, ITestRunnerListener
|
||||
{
|
||||
public void RunStarted(ITest testsToRun)
|
||||
{
|
||||
}
|
||||
|
||||
public void RunFinished(ITestResult testResults)
|
||||
{
|
||||
IsRunning = false;
|
||||
}
|
||||
|
||||
public void TestStarted(ITest test)
|
||||
{
|
||||
}
|
||||
|
||||
public void TestFinished(ITestResult result)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public T AddEventHandler<T>() where T : ScriptableObject, ITestRunnerListener
|
||||
{
|
||||
return m_EditModeRunner.AddEventHandler<T>();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class EditModeLauncherContextSettings : IDisposable
|
||||
{
|
||||
private bool m_RunInBackground;
|
||||
|
||||
public EditModeLauncherContextSettings()
|
||||
{
|
||||
SetupProjectParameters();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
CleanupProjectParameters();
|
||||
}
|
||||
|
||||
private void SetupProjectParameters()
|
||||
{
|
||||
m_RunInBackground = Application.runInBackground;
|
||||
Application.runInBackground = true;
|
||||
}
|
||||
|
||||
private void CleanupProjectParameters()
|
||||
{
|
||||
Application.runInBackground = m_RunInBackground;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,66 @@
|
||||
using System;
|
||||
using UnityEngine;
|
||||
using System.Net;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class AndroidPlatformSetup : IPlatformSetup
|
||||
{
|
||||
private string m_oldApplicationIdentifier;
|
||||
private string m_oldDeviceSocketAddress;
|
||||
[SerializeField]
|
||||
private bool m_Stripping;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
m_oldApplicationIdentifier = PlayerSettings.GetApplicationIdentifier(BuildTargetGroup.Android);
|
||||
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, "com.UnityTestRunner.UnityTestRunner");
|
||||
|
||||
m_oldDeviceSocketAddress = EditorUserBuildSettings.androidDeviceSocketAddress;
|
||||
var androidDeviceConnection = Environment.GetEnvironmentVariable("ANDROID_DEVICE_CONNECTION");
|
||||
EditorUserBuildSettings.waitForPlayerConnection = true;
|
||||
if (androidDeviceConnection != null)
|
||||
{
|
||||
EditorUserBuildSettings.androidDeviceSocketAddress = androidDeviceConnection;
|
||||
}
|
||||
m_Stripping = PlayerSettings.stripEngineCode;
|
||||
PlayerSettings.stripEngineCode = false;
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
PlayerSettings.stripEngineCode = m_Stripping;
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
var connectionResult = -1;
|
||||
var maxTryCount = 10;
|
||||
var tryCount = maxTryCount;
|
||||
while (tryCount-- > 0 && connectionResult == -1)
|
||||
{
|
||||
connectionResult = EditorConnectionInternal.ConnectPlayerProxy(IPAddress.Loopback.ToString(), 34999);
|
||||
if (EditorUtility.DisplayCancelableProgressBar("Editor Connection", "Connecting to the player",
|
||||
1 - ((float)tryCount / maxTryCount)))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
throw new TestLaunchFailedException();
|
||||
}
|
||||
}
|
||||
EditorUtility.ClearProgressBar();
|
||||
if (connectionResult == -1)
|
||||
throw new TestLaunchFailedException(
|
||||
"Timed out trying to connect to the player. Player failed to launch or crashed soon after launching");
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
EditorUserBuildSettings.androidDeviceSocketAddress = m_oldDeviceSocketAddress;
|
||||
PlayerSettings.SetApplicationIdentifier(BuildTargetGroup.Android, m_oldApplicationIdentifier);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,42 @@
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
[Serializable]
|
||||
internal class ApplePlatformSetup : IPlatformSetup
|
||||
{
|
||||
[SerializeField]
|
||||
private bool m_Stripping;
|
||||
|
||||
public ApplePlatformSetup(BuildTarget buildTarget)
|
||||
{
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
// Camera and fonts are stripped out and app crashes on iOS when test runner is trying to add a scene with... camera and text
|
||||
m_Stripping = PlayerSettings.stripEngineCode;
|
||||
PlayerSettings.stripEngineCode = false;
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
// Restoring player setting as early as possible
|
||||
PlayerSettings.stripEngineCode = m_Stripping;
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,11 @@
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal interface IPlatformSetup
|
||||
{
|
||||
void Setup();
|
||||
void PostBuildAction();
|
||||
void PostSuccessfulBuildAction();
|
||||
void PostSuccessfulLaunchAction();
|
||||
void CleanUp();
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class LuminPlatformSetup : IPlatformSetup
|
||||
{
|
||||
private const string kDeviceAddress = "127.0.0.1";
|
||||
private const int kDevicePort = 55000;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
var connectionResult = -1;
|
||||
var maxTryCount = 100;
|
||||
var tryCount = maxTryCount;
|
||||
while (tryCount-- > 0 && connectionResult == -1)
|
||||
{
|
||||
Thread.Sleep(1000);
|
||||
connectionResult = EditorConnectionInternal.ConnectPlayerProxy(kDeviceAddress, kDevicePort);
|
||||
if (EditorUtility.DisplayCancelableProgressBar("Editor Connection", "Connecting to the player",
|
||||
1 - ((float)tryCount / maxTryCount)))
|
||||
{
|
||||
EditorUtility.ClearProgressBar();
|
||||
throw new TestLaunchFailedException();
|
||||
}
|
||||
}
|
||||
EditorUtility.ClearProgressBar();
|
||||
if (connectionResult == -1)
|
||||
throw new TestLaunchFailedException(
|
||||
"Timed out trying to connect to the player. Player failed to launch or crashed soon after launching");
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,123 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
[Serializable]
|
||||
internal class PlatformSpecificSetup
|
||||
{
|
||||
[SerializeField]
|
||||
private ApplePlatformSetup m_AppleiOSPlatformSetup = new ApplePlatformSetup(BuildTarget.iOS);
|
||||
[SerializeField]
|
||||
private ApplePlatformSetup m_AppleTvOSPlatformSetup = new ApplePlatformSetup(BuildTarget.tvOS);
|
||||
[SerializeField]
|
||||
private XboxOnePlatformSetup m_XboxOnePlatformSetup = new XboxOnePlatformSetup();
|
||||
[SerializeField]
|
||||
private AndroidPlatformSetup m_AndroidPlatformSetup = new AndroidPlatformSetup();
|
||||
[SerializeField]
|
||||
private SwitchPlatformSetup m_SwitchPlatformSetup = new SwitchPlatformSetup();
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
[SerializeField]
|
||||
private StadiaPlatformSetup m_StadiaPlatformSetup = new StadiaPlatformSetup();
|
||||
#endif
|
||||
[SerializeField]
|
||||
private UwpPlatformSetup m_UwpPlatformSetup = new UwpPlatformSetup();
|
||||
|
||||
[SerializeField]
|
||||
private LuminPlatformSetup m_LuminPlatformSetup = new LuminPlatformSetup();
|
||||
|
||||
|
||||
private IDictionary<BuildTarget, IPlatformSetup> m_SetupTypes;
|
||||
|
||||
[SerializeField]
|
||||
private BuildTarget m_Target;
|
||||
|
||||
public PlatformSpecificSetup()
|
||||
{
|
||||
}
|
||||
|
||||
public PlatformSpecificSetup(BuildTarget target)
|
||||
{
|
||||
m_Target = target;
|
||||
}
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
var dictionary = GetSetup();
|
||||
|
||||
if (!dictionary.ContainsKey(m_Target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary[m_Target].Setup();
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
var dictionary = GetSetup();
|
||||
|
||||
if (!dictionary.ContainsKey(m_Target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary[m_Target].PostBuildAction();
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
var dictionary = GetSetup();
|
||||
|
||||
if (!dictionary.ContainsKey(m_Target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary[m_Target].PostSuccessfulBuildAction();
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
var dictionary = GetSetup();
|
||||
|
||||
if (!dictionary.ContainsKey(m_Target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary[m_Target].PostSuccessfulLaunchAction();
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
var dictionary = GetSetup();
|
||||
|
||||
if (!dictionary.ContainsKey(m_Target))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
dictionary[m_Target].CleanUp();
|
||||
}
|
||||
|
||||
private IDictionary<BuildTarget, IPlatformSetup> GetSetup()
|
||||
{
|
||||
m_SetupTypes = new Dictionary<BuildTarget, IPlatformSetup>()
|
||||
{
|
||||
{BuildTarget.iOS, m_AppleiOSPlatformSetup},
|
||||
{BuildTarget.tvOS, m_AppleTvOSPlatformSetup},
|
||||
{BuildTarget.XboxOne, m_XboxOnePlatformSetup},
|
||||
{BuildTarget.Android, m_AndroidPlatformSetup},
|
||||
{BuildTarget.WSAPlayer, m_UwpPlatformSetup},
|
||||
{BuildTarget.Lumin, m_LuminPlatformSetup},
|
||||
#if UNITY_2019_3_OR_NEWER
|
||||
{BuildTarget.Stadia, m_StadiaPlatformSetup},
|
||||
#endif
|
||||
{BuildTarget.Switch, m_SwitchPlatformSetup}
|
||||
};
|
||||
return m_SetupTypes;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,25 @@
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class StadiaPlatformSetup : IPlatformSetup
|
||||
{
|
||||
public void Setup()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,41 @@
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class SwitchPlatformSetup : IPlatformSetup
|
||||
{
|
||||
public void Setup()
|
||||
{
|
||||
EditorUserBuildSettings.switchCreateRomFile = true;
|
||||
EditorUserBuildSettings.switchNVNGraphicsDebugger = false;
|
||||
#if UNITY_2020_1_OR_NEWER
|
||||
EditorUserBuildSettings.switchNVNDrawValidation_Heavy = true; // catches more graphics errors
|
||||
#else
|
||||
EditorUserBuildSettings.switchNVNDrawValidation = true; // catches more graphics errors
|
||||
#endif
|
||||
EditorUserBuildSettings.development = true;
|
||||
EditorUserBuildSettings.switchRedirectWritesToHostMount = true;
|
||||
|
||||
// We can use these when more debugging is required:
|
||||
//EditorUserBuildSettings.switchNVNDrawValidation = false; // cannot be used with shader debug
|
||||
//EditorUserBuildSettings.switchNVNGraphicsDebugger = true;
|
||||
//EditorUserBuildSettings.switchNVNShaderDebugging = true;
|
||||
//EditorUserBuildSettings.switchCreateSolutionFile = true; // for shorter iteration time
|
||||
//EditorUserBuildSettings.allowDebugging = true; // managed debugger can be attached
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,52 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class UwpPlatformSetup : IPlatformSetup
|
||||
{
|
||||
private const string k_SettingsBuildConfiguration = "BuildConfiguration";
|
||||
private bool m_InternetClientServer;
|
||||
private bool m_PrivateNetworkClientServer;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
m_InternetClientServer = PlayerSettings.WSA.GetCapability(PlayerSettings.WSACapability.InternetClientServer);
|
||||
m_PrivateNetworkClientServer = PlayerSettings.WSA.GetCapability(PlayerSettings.WSACapability.PrivateNetworkClientServer);
|
||||
PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.InternetClientServer, true);
|
||||
PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.PrivateNetworkClientServer, true);
|
||||
|
||||
// This setting is initialized only when Window Store App is selected from the Build Settings window, and
|
||||
// is typically an empty strings when running tests via UTR on the command-line.
|
||||
bool wsaSettingNotInitialized = string.IsNullOrEmpty(EditorUserBuildSettings.wsaArchitecture);
|
||||
|
||||
// If WSA build settings aren't fully initialized or running from a build machine, specify a default build configuration.
|
||||
// Otherwise we can use the existing configuration specified by the user in Build Settings.
|
||||
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("UNITY_THISISABUILDMACHINE")) || wsaSettingNotInitialized)
|
||||
{
|
||||
EditorUserBuildSettings.wsaSubtarget = WSASubtarget.PC;
|
||||
EditorUserBuildSettings.wsaArchitecture = "x64";
|
||||
EditorUserBuildSettings.SetPlatformSettings(BuildPipeline.GetBuildTargetName(BuildTarget.WSAPlayer), k_SettingsBuildConfiguration, WSABuildType.Debug.ToString());
|
||||
EditorUserBuildSettings.wsaUWPBuildType = WSAUWPBuildType.ExecutableOnly;
|
||||
PlayerSettings.SetIl2CppCompilerConfiguration(BuildTargetGroup.WSA, Il2CppCompilerConfiguration.Debug);
|
||||
}
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.InternetClientServer, m_InternetClientServer);
|
||||
PlayerSettings.WSA.SetCapability(PlayerSettings.WSACapability.PrivateNetworkClientServer, m_PrivateNetworkClientServer);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,47 @@
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class XboxOnePlatformSetup : IPlatformSetup
|
||||
{
|
||||
private XboxOneDeployMethod oldXboxOneDeployMethod;
|
||||
private XboxOneDeployDrive oldXboxOneDeployDrive;
|
||||
private string oldXboxOneAdditionalDebugPorts;
|
||||
|
||||
public void Setup()
|
||||
{
|
||||
oldXboxOneDeployMethod = EditorUserBuildSettings.xboxOneDeployMethod;
|
||||
oldXboxOneDeployDrive = EditorUserBuildSettings.xboxOneDeployDrive;
|
||||
oldXboxOneAdditionalDebugPorts = EditorUserBuildSettings.xboxOneAdditionalDebugPorts;
|
||||
|
||||
EditorUserBuildSettings.xboxOneDeployMethod = XboxOneDeployMethod.Package;
|
||||
EditorUserBuildSettings.xboxOneDeployDrive = XboxOneDeployDrive.Default;
|
||||
|
||||
// This causes the XboxOne post processing systems to open this port in your package manifest.
|
||||
// In addition it will open the ephemeral range for debug connections as well.
|
||||
// Failure to do this will cause connection problems.
|
||||
EditorUserBuildSettings.xboxOneAdditionalDebugPorts = "34999";
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
EditorUserBuildSettings.xboxOneDeployMethod = oldXboxOneDeployMethod;
|
||||
EditorUserBuildSettings.xboxOneDeployDrive = oldXboxOneDeployDrive;
|
||||
|
||||
// This causes the XboxOne post processing systems to open this port in your package manifest.
|
||||
// In addition it will open the ephemeral range for debug connections as well.
|
||||
// Failure to do this will cause connection problems.
|
||||
EditorUserBuildSettings.xboxOneAdditionalDebugPorts = oldXboxOneAdditionalDebugPorts;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,255 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
using NUnit.Framework.Internal.Filters;
|
||||
using UnityEditor;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEditor.TestRunner.TestLaunchers;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestRunner.Utils;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
using UnityEngine.TestTools.TestRunner.Callbacks;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class TestLaunchFailedException : Exception
|
||||
{
|
||||
public TestLaunchFailedException() {}
|
||||
public TestLaunchFailedException(string message) : base(message) {}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
internal class PlayerLauncher : RuntimeTestLauncherBase
|
||||
{
|
||||
private readonly PlaymodeTestsControllerSettings m_Settings;
|
||||
private readonly BuildTarget m_TargetPlatform;
|
||||
private ITestRunSettings m_OverloadTestRunSettings;
|
||||
private string m_SceneName;
|
||||
private int m_HeartbeatTimeout;
|
||||
|
||||
public PlayerLauncher(PlaymodeTestsControllerSettings settings, BuildTarget? targetPlatform, ITestRunSettings overloadTestRunSettings, int heartbeatTimeout)
|
||||
{
|
||||
m_Settings = settings;
|
||||
m_TargetPlatform = targetPlatform ?? EditorUserBuildSettings.activeBuildTarget;
|
||||
m_OverloadTestRunSettings = overloadTestRunSettings;
|
||||
m_HeartbeatTimeout = heartbeatTimeout;
|
||||
}
|
||||
|
||||
protected override RuntimePlatform? TestTargetPlatform
|
||||
{
|
||||
get { return BuildTargetConverter.TryConvertToRuntimePlatform(m_TargetPlatform); }
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
var editorConnectionTestCollector = RemoteTestRunController.instance;
|
||||
editorConnectionTestCollector.hideFlags = HideFlags.HideAndDontSave;
|
||||
editorConnectionTestCollector.Init(m_TargetPlatform, m_HeartbeatTimeout);
|
||||
|
||||
var remotePlayerLogController = RemotePlayerLogController.instance;
|
||||
remotePlayerLogController.hideFlags = HideFlags.HideAndDontSave;
|
||||
|
||||
using (var settings = new PlayerLauncherContextSettings(m_OverloadTestRunSettings))
|
||||
{
|
||||
m_SceneName = CreateSceneName();
|
||||
var scene = PrepareScene(m_SceneName);
|
||||
string scenePath = scene.path;
|
||||
|
||||
var filter = m_Settings.BuildNUnitFilter();
|
||||
var runner = LoadTests(filter);
|
||||
var exceptionThrown = ExecutePreBuildSetupMethods(runner.LoadedTest, filter);
|
||||
if (exceptionThrown)
|
||||
{
|
||||
ReopenOriginalScene(m_Settings.originalScene);
|
||||
AssetDatabase.DeleteAsset(m_SceneName);
|
||||
CallbacksDelegator.instance.RunFailed("Run Failed: One or more errors in a prebuild setup. See the editor log for details.");
|
||||
return;
|
||||
}
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
EditorSceneManager.SaveScene(scene);
|
||||
|
||||
var playerBuildOptions = GetBuildOptions(scenePath);
|
||||
|
||||
var success = BuildAndRunPlayer(playerBuildOptions);
|
||||
|
||||
editorConnectionTestCollector.PostBuildAction();
|
||||
ExecutePostBuildCleanupMethods(runner.LoadedTest, filter);
|
||||
|
||||
ReopenOriginalScene(m_Settings.originalScene);
|
||||
AssetDatabase.DeleteAsset(m_SceneName);
|
||||
|
||||
if (!success)
|
||||
{
|
||||
editorConnectionTestCollector.CleanUp();
|
||||
ScriptableObject.DestroyImmediate(editorConnectionTestCollector);
|
||||
Debug.LogError("Player build failed");
|
||||
throw new TestLaunchFailedException("Player build failed");
|
||||
}
|
||||
|
||||
if ((playerBuildOptions.BuildPlayerOptions.options & BuildOptions.AutoRunPlayer) != 0)
|
||||
{
|
||||
editorConnectionTestCollector.PostSuccessfulBuildAction();
|
||||
editorConnectionTestCollector.PostSuccessfulLaunchAction();
|
||||
}
|
||||
|
||||
var runSettings = m_OverloadTestRunSettings as PlayerLauncherTestRunSettings;
|
||||
if (success && runSettings != null && runSettings.buildOnly)
|
||||
{
|
||||
EditorUtility.RevealInFinder(playerBuildOptions.BuildPlayerOptions.locationPathName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public Scene PrepareScene(string sceneName)
|
||||
{
|
||||
var scene = CreateBootstrapScene(sceneName, runner =>
|
||||
{
|
||||
runner.AddEventHandlerMonoBehaviour<PlayModeRunnerCallback>();
|
||||
runner.settings = m_Settings;
|
||||
var commandLineArgs = Environment.GetCommandLineArgs();
|
||||
if (!commandLineArgs.Contains("-doNotReportTestResultsBackToEditor"))
|
||||
{
|
||||
runner.AddEventHandlerMonoBehaviour<RemoteTestResultSender>();
|
||||
}
|
||||
runner.AddEventHandlerMonoBehaviour<PlayerQuitHandler>();
|
||||
runner.AddEventHandlerScriptableObject<TestRunCallbackListener>();
|
||||
});
|
||||
return scene;
|
||||
}
|
||||
|
||||
private static bool BuildAndRunPlayer(PlayerLauncherBuildOptions buildOptions)
|
||||
{
|
||||
Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Building player with following options:\n{0}", buildOptions);
|
||||
|
||||
|
||||
// Android has to be in listen mode to establish player connection
|
||||
if (buildOptions.BuildPlayerOptions.target == BuildTarget.Android)
|
||||
{
|
||||
buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
|
||||
}
|
||||
|
||||
// For now, so does Lumin
|
||||
if (buildOptions.BuildPlayerOptions.target == BuildTarget.Lumin)
|
||||
{
|
||||
buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
|
||||
}
|
||||
|
||||
var result = BuildPipeline.BuildPlayer(buildOptions.BuildPlayerOptions);
|
||||
if (result.summary.result != Build.Reporting.BuildResult.Succeeded)
|
||||
Debug.LogError(result.SummarizeErrors());
|
||||
|
||||
return result.summary.result == Build.Reporting.BuildResult.Succeeded;
|
||||
}
|
||||
|
||||
internal PlayerLauncherBuildOptions GetBuildOptions(string scenePath)
|
||||
{
|
||||
var buildOnly = false;
|
||||
var runSettings = m_OverloadTestRunSettings as PlayerLauncherTestRunSettings;
|
||||
if (runSettings != null)
|
||||
{
|
||||
buildOnly = runSettings.buildOnly;
|
||||
}
|
||||
|
||||
var buildOptions = new BuildPlayerOptions();
|
||||
|
||||
var scenes = new List<string>() { scenePath };
|
||||
scenes.AddRange(EditorBuildSettings.scenes.Select(x => x.path));
|
||||
buildOptions.scenes = scenes.ToArray();
|
||||
|
||||
buildOptions.options |= BuildOptions.Development | BuildOptions.ConnectToHost | BuildOptions.IncludeTestAssemblies | BuildOptions.StrictMode;
|
||||
buildOptions.target = m_TargetPlatform;
|
||||
|
||||
if (EditorUserBuildSettings.waitForPlayerConnection)
|
||||
buildOptions.options |= BuildOptions.WaitForPlayerConnection;
|
||||
|
||||
if (EditorUserBuildSettings.allowDebugging)
|
||||
buildOptions.options |= BuildOptions.AllowDebugging;
|
||||
|
||||
if (EditorUserBuildSettings.installInBuildFolder)
|
||||
buildOptions.options |= BuildOptions.InstallInBuildFolder;
|
||||
else if (!buildOnly)
|
||||
buildOptions.options |= BuildOptions.AutoRunPlayer;
|
||||
|
||||
var buildTargetGroup = EditorUserBuildSettings.activeBuildTargetGroup;
|
||||
|
||||
//Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
|
||||
if (PostprocessBuildPlayer.SupportsLz4Compression(buildTargetGroup, m_TargetPlatform))
|
||||
{
|
||||
if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4)
|
||||
buildOptions.options |= BuildOptions.CompressWithLz4;
|
||||
else if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4HC)
|
||||
buildOptions.options |= BuildOptions.CompressWithLz4HC;
|
||||
}
|
||||
|
||||
string buildLocation;
|
||||
if (buildOnly)
|
||||
{
|
||||
buildLocation = buildOptions.locationPathName = runSettings.buildOnlyLocationPath;
|
||||
}
|
||||
else
|
||||
{
|
||||
var reduceBuildLocationPathLength = false;
|
||||
|
||||
//Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
|
||||
if ((m_TargetPlatform == BuildTarget.WSAPlayer) || (m_TargetPlatform == BuildTarget.XboxOne))
|
||||
{
|
||||
reduceBuildLocationPathLength = true;
|
||||
}
|
||||
|
||||
var uniqueTempPathInProject = FileUtil.GetUniqueTempPathInProject();
|
||||
var playerDirectoryName = reduceBuildLocationPathLength ? "PwT" : "PlayerWithTests";
|
||||
|
||||
if (reduceBuildLocationPathLength)
|
||||
{
|
||||
uniqueTempPathInProject = Path.GetTempFileName();
|
||||
File.Delete(uniqueTempPathInProject);
|
||||
Directory.CreateDirectory(uniqueTempPathInProject);
|
||||
}
|
||||
|
||||
var tempPath = Path.GetFullPath(uniqueTempPathInProject);
|
||||
buildLocation = Path.Combine(tempPath, playerDirectoryName);
|
||||
|
||||
// iOS builds create a folder with Xcode project instead of an executable, therefore no executable name is added
|
||||
if (m_TargetPlatform == BuildTarget.iOS)
|
||||
{
|
||||
buildOptions.locationPathName = buildLocation;
|
||||
}
|
||||
else
|
||||
{
|
||||
string extensionForBuildTarget =
|
||||
PostprocessBuildPlayer.GetExtensionForBuildTarget(buildTargetGroup, buildOptions.target,
|
||||
buildOptions.options);
|
||||
var playerExecutableName = "PlayerWithTests";
|
||||
playerExecutableName += string.Format(".{0}", extensionForBuildTarget);
|
||||
buildOptions.locationPathName = Path.Combine(buildLocation, playerExecutableName);
|
||||
}
|
||||
}
|
||||
|
||||
return new PlayerLauncherBuildOptions
|
||||
{
|
||||
BuildPlayerOptions = ModifyBuildOptions(buildOptions),
|
||||
PlayerDirectory = buildLocation,
|
||||
};
|
||||
}
|
||||
|
||||
private BuildPlayerOptions ModifyBuildOptions(BuildPlayerOptions buildOptions)
|
||||
{
|
||||
var allAssemblies = AppDomain.CurrentDomain.GetAssemblies()
|
||||
.Where(x => x.GetReferencedAssemblies().Any(z => z.Name == "UnityEditor.TestRunner")).ToArray();
|
||||
var attributes = allAssemblies.SelectMany(assembly => assembly.GetCustomAttributes(typeof(TestPlayerBuildModifierAttribute), true).OfType<TestPlayerBuildModifierAttribute>()).ToArray();
|
||||
var modifiers = attributes.Select(attribute => attribute.ConstructModifier()).ToArray();
|
||||
|
||||
foreach (var modifier in modifiers)
|
||||
{
|
||||
buildOptions = modifier.ModifyOptions(buildOptions);
|
||||
}
|
||||
|
||||
return buildOptions;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,23 @@
|
||||
using System.Text;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class PlayerLauncherBuildOptions
|
||||
{
|
||||
public BuildPlayerOptions BuildPlayerOptions;
|
||||
public string PlayerDirectory;
|
||||
|
||||
public override string ToString()
|
||||
{
|
||||
var str = new StringBuilder();
|
||||
str.AppendLine("locationPathName = " + BuildPlayerOptions.locationPathName);
|
||||
str.AppendLine("target = " + BuildPlayerOptions.target);
|
||||
str.AppendLine("scenes = " + string.Join(", ", BuildPlayerOptions.scenes));
|
||||
str.AppendLine("assetBundleManifestPath = " + BuildPlayerOptions.assetBundleManifestPath);
|
||||
str.AppendLine("options.Development = " + ((BuildPlayerOptions.options & BuildOptions.Development) != 0));
|
||||
str.AppendLine("options.AutoRunPlayer = " + ((BuildPlayerOptions.options & BuildOptions.AutoRunPlayer) != 0));
|
||||
str.AppendLine("options.ForceEnableAssertions = " + ((BuildPlayerOptions.options & BuildOptions.ForceEnableAssertions) != 0));
|
||||
return str.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,113 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class PlayerLauncherContextSettings : IDisposable
|
||||
{
|
||||
private ITestRunSettings m_OverloadSettings;
|
||||
|
||||
private EditorBuildSettingsScene[] m_EditorBuildSettings;
|
||||
#pragma warning disable 618
|
||||
private ResolutionDialogSetting m_DisplayResolutionDialog;
|
||||
#pragma warning restore 618
|
||||
private bool m_RunInBackground;
|
||||
private FullScreenMode m_FullScreenMode;
|
||||
private bool m_ResizableWindow;
|
||||
private bool m_ShowUnitySplashScreen;
|
||||
private string m_OldproductName;
|
||||
private string m_OldAotOptions;
|
||||
#pragma warning disable 618
|
||||
private Lightmapping.GIWorkflowMode m_OldLightmapping;
|
||||
#pragma warning restore 618
|
||||
private bool m_explicitNullChecks;
|
||||
|
||||
private bool m_Disposed;
|
||||
|
||||
public PlayerLauncherContextSettings(ITestRunSettings overloadSettings)
|
||||
{
|
||||
m_OverloadSettings = overloadSettings;
|
||||
SetupProjectParameters();
|
||||
|
||||
if (overloadSettings != null)
|
||||
{
|
||||
overloadSettings.Apply();
|
||||
}
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
if (!m_Disposed)
|
||||
{
|
||||
CleanupProjectParameters();
|
||||
if (m_OverloadSettings != null)
|
||||
{
|
||||
m_OverloadSettings.Dispose();
|
||||
}
|
||||
|
||||
m_Disposed = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void SetupProjectParameters()
|
||||
{
|
||||
EditorApplication.LockReloadAssemblies();
|
||||
|
||||
m_EditorBuildSettings = EditorBuildSettings.scenes;
|
||||
|
||||
#pragma warning disable 618
|
||||
m_DisplayResolutionDialog = PlayerSettings.displayResolutionDialog;
|
||||
PlayerSettings.displayResolutionDialog = ResolutionDialogSetting.Disabled;
|
||||
#pragma warning restore 618
|
||||
|
||||
m_RunInBackground = PlayerSettings.runInBackground;
|
||||
PlayerSettings.runInBackground = true;
|
||||
|
||||
m_FullScreenMode = PlayerSettings.fullScreenMode;
|
||||
PlayerSettings.fullScreenMode = FullScreenMode.Windowed;
|
||||
|
||||
m_OldAotOptions = PlayerSettings.aotOptions;
|
||||
PlayerSettings.aotOptions = "nimt-trampolines=1024";
|
||||
|
||||
m_ResizableWindow = PlayerSettings.resizableWindow;
|
||||
PlayerSettings.resizableWindow = true;
|
||||
|
||||
m_ShowUnitySplashScreen = PlayerSettings.SplashScreen.show;
|
||||
PlayerSettings.SplashScreen.show = false;
|
||||
|
||||
m_OldproductName = PlayerSettings.productName;
|
||||
PlayerSettings.productName = string.Join("_", Application.productName.Split(Path.GetInvalidFileNameChars()));
|
||||
|
||||
#pragma warning disable 618
|
||||
m_OldLightmapping = Lightmapping.giWorkflowMode;
|
||||
Lightmapping.giWorkflowMode = Lightmapping.GIWorkflowMode.OnDemand;
|
||||
#pragma warning restore 618
|
||||
|
||||
m_explicitNullChecks = EditorUserBuildSettings.explicitNullChecks;
|
||||
EditorUserBuildSettings.explicitNullChecks = true;
|
||||
}
|
||||
|
||||
private void CleanupProjectParameters()
|
||||
{
|
||||
EditorBuildSettings.scenes = m_EditorBuildSettings;
|
||||
|
||||
PlayerSettings.fullScreenMode = m_FullScreenMode;
|
||||
PlayerSettings.runInBackground = m_RunInBackground;
|
||||
#pragma warning disable 618
|
||||
PlayerSettings.displayResolutionDialog = m_DisplayResolutionDialog;
|
||||
#pragma warning restore 618
|
||||
PlayerSettings.resizableWindow = m_ResizableWindow;
|
||||
PlayerSettings.SplashScreen.show = m_ShowUnitySplashScreen;
|
||||
PlayerSettings.productName = m_OldproductName;
|
||||
PlayerSettings.aotOptions = m_OldAotOptions;
|
||||
#pragma warning disable 618
|
||||
Lightmapping.giWorkflowMode = m_OldLightmapping;
|
||||
#pragma warning restore 618
|
||||
EditorUserBuildSettings.explicitNullChecks = m_explicitNullChecks;
|
||||
|
||||
EditorApplication.UnlockReloadAssemblies();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,19 @@
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
class PlayerLauncherTestRunSettings : ITestRunSettings
|
||||
{
|
||||
public bool buildOnly { set; get; }
|
||||
|
||||
public string buildOnlyLocationPath { set; get; }
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
void ITestRunSettings.Apply()
|
||||
{
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,133 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal.Filters;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestRunner.Utils;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
using UnityEngine.TestTools.TestRunner.Callbacks;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class PlaymodeLauncher : RuntimeTestLauncherBase
|
||||
{
|
||||
public static bool IsRunning;
|
||||
private Scene m_Scene;
|
||||
private bool m_IsTestSetupPerformed;
|
||||
private readonly PlaymodeTestsControllerSettings m_Settings;
|
||||
private ITestFilter testFilter;
|
||||
|
||||
[SerializeField]
|
||||
private List<Type> m_EventHandlers = new List<Type>();
|
||||
|
||||
public PlaymodeLauncher(PlaymodeTestsControllerSettings settings)
|
||||
{
|
||||
m_Settings = settings;
|
||||
}
|
||||
|
||||
public override void Run()
|
||||
{
|
||||
IsRunning = true;
|
||||
ConsoleWindow.SetConsoleErrorPause(false);
|
||||
Application.runInBackground = true;
|
||||
|
||||
var sceneName = CreateSceneName();
|
||||
m_Scene = CreateBootstrapScene(sceneName, runner =>
|
||||
{
|
||||
runner.AddEventHandlerMonoBehaviour<PlayModeRunnerCallback>();
|
||||
runner.AddEventHandlerScriptableObject<TestRunnerCallback>();
|
||||
runner.AddEventHandlerScriptableObject<CallbacksDelegatorListener>();
|
||||
runner.AddEventHandlerScriptableObject<TestRunCallbackListener>();
|
||||
|
||||
foreach (var eventHandler in m_EventHandlers)
|
||||
{
|
||||
var obj = ScriptableObject.CreateInstance(eventHandler);
|
||||
runner.AddEventHandlerScriptableObject(obj as ITestRunnerListener);
|
||||
}
|
||||
|
||||
runner.settings = m_Settings;
|
||||
});
|
||||
|
||||
if (m_Settings.sceneBased)
|
||||
{
|
||||
var newListOfScenes =
|
||||
new List<EditorBuildSettingsScene> {new EditorBuildSettingsScene(sceneName, true)};
|
||||
newListOfScenes.AddRange(EditorBuildSettings.scenes);
|
||||
EditorBuildSettings.scenes = newListOfScenes.ToArray();
|
||||
}
|
||||
|
||||
EditorApplication.update += UpdateCallback;
|
||||
}
|
||||
|
||||
public void UpdateCallback()
|
||||
{
|
||||
if (m_IsTestSetupPerformed)
|
||||
{
|
||||
if (m_Scene.IsValid())
|
||||
SceneManager.SetActiveScene(m_Scene);
|
||||
EditorApplication.update -= UpdateCallback;
|
||||
EditorApplication.isPlaying = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
testFilter = m_Settings.BuildNUnitFilter();
|
||||
var runner = LoadTests(testFilter);
|
||||
|
||||
var exceptionThrown = ExecutePreBuildSetupMethods(runner.LoadedTest, testFilter);
|
||||
if (exceptionThrown)
|
||||
{
|
||||
EditorApplication.update -= UpdateCallback;
|
||||
IsRunning = false;
|
||||
var controller = PlaymodeTestsController.GetController();
|
||||
ReopenOriginalScene(controller);
|
||||
AssetDatabase.DeleteAsset(controller.settings.bootstrapScene);
|
||||
CallbacksDelegator.instance.RunFailed("Run Failed: One or more errors in a prebuild setup. See the editor log for details.");
|
||||
return;
|
||||
}
|
||||
m_IsTestSetupPerformed = true;
|
||||
}
|
||||
}
|
||||
|
||||
[InitializeOnLoad]
|
||||
public class BackgroundWatcher
|
||||
{
|
||||
static BackgroundWatcher()
|
||||
{
|
||||
EditorApplication.playModeStateChanged += OnPlayModeStateChanged;
|
||||
}
|
||||
|
||||
private static void OnPlayModeStateChanged(PlayModeStateChange state)
|
||||
{
|
||||
if (!PlaymodeTestsController.IsControllerOnScene())
|
||||
return;
|
||||
var runner = PlaymodeTestsController.GetController();
|
||||
if (runner == null)
|
||||
return;
|
||||
if (state == PlayModeStateChange.ExitingPlayMode)
|
||||
{
|
||||
AssetDatabase.DeleteAsset(runner.settings.bootstrapScene);
|
||||
ExecutePostBuildCleanupMethods(runner.m_Runner.LoadedTest, runner.settings.BuildNUnitFilter(), Application.platform);
|
||||
IsRunning = false;
|
||||
}
|
||||
else if (state == PlayModeStateChange.EnteredEditMode)
|
||||
{
|
||||
//reopen the original scene once we exit playmode
|
||||
ReopenOriginalScene(runner);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected static void ReopenOriginalScene(PlaymodeTestsController runner)
|
||||
{
|
||||
ReopenOriginalScene(runner.settings.originalScene);
|
||||
}
|
||||
|
||||
public void AddEventHandler<T>() where T : ScriptableObject, ITestRunnerListener
|
||||
{
|
||||
m_EventHandlers.Add(typeof(T));
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class PostbuildCleanupAttributeFinder : AttributeFinderBase<IPostBuildCleanup, PostBuildCleanupAttribute>
|
||||
{
|
||||
public PostbuildCleanupAttributeFinder() : base(attribute => attribute.TargetClass) {}
|
||||
}
|
||||
}
|
@@ -0,0 +1,9 @@
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal class PrebuildSetupAttributeFinder : AttributeFinderBase<IPrebuildSetup, PrebuildSetupAttribute>
|
||||
{
|
||||
public PrebuildSetupAttributeFinder() : base((attribute) => attribute.TargetClass) {}
|
||||
}
|
||||
}
|
@@ -0,0 +1,89 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using UnityEditor.DeploymentTargets;
|
||||
using UnityEditor.TestTools.TestRunner.CommandLineTest;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestRunner.TestLaunchers
|
||||
{
|
||||
[Serializable]
|
||||
internal class RemotePlayerLogController : ScriptableSingleton<RemotePlayerLogController>
|
||||
{
|
||||
private List<LogWriter> m_LogWriters;
|
||||
|
||||
private Dictionary<string, DeploymentTargetLogger> m_Loggers;
|
||||
|
||||
private string m_DeviceLogsDirectory;
|
||||
|
||||
public void SetBuildTarget(BuildTarget buildTarget)
|
||||
{
|
||||
m_Loggers = GetDeploymentTargetLoggers(buildTarget);
|
||||
|
||||
if (m_Loggers == null)
|
||||
Debug.Log("Deployment target logger could not be created");
|
||||
}
|
||||
|
||||
public void SetLogsDirectory(string dir)
|
||||
{
|
||||
m_DeviceLogsDirectory = dir;
|
||||
}
|
||||
|
||||
public void StartLogWriters()
|
||||
{
|
||||
if (m_DeviceLogsDirectory == null || m_Loggers == null)
|
||||
return;
|
||||
|
||||
m_LogWriters = new List<LogWriter>();
|
||||
|
||||
foreach (var logger in m_Loggers)
|
||||
{
|
||||
m_LogWriters.Add(new LogWriter(m_DeviceLogsDirectory, logger.Key, logger.Value));
|
||||
logger.Value.Start();
|
||||
}
|
||||
}
|
||||
|
||||
public void StopLogWriters()
|
||||
{
|
||||
if (m_LogWriters == null)
|
||||
return;
|
||||
|
||||
foreach (var logWriter in m_LogWriters)
|
||||
{
|
||||
logWriter.Stop();
|
||||
}
|
||||
}
|
||||
|
||||
private Dictionary<string, DeploymentTargetLogger> GetDeploymentTargetLoggers(BuildTarget buildTarget)
|
||||
{
|
||||
DeploymentTargetManager deploymentTargetManager;
|
||||
|
||||
try
|
||||
{
|
||||
deploymentTargetManager = DeploymentTargetManager.CreateInstance(EditorUserBuildSettings.activeBuildTargetGroup, buildTarget);
|
||||
|
||||
if (deploymentTargetManager == null)
|
||||
return null;
|
||||
}
|
||||
catch (NotSupportedException ex)
|
||||
{
|
||||
Debug.Log(ex.Message);
|
||||
Debug.Log("Deployment target logger not initialised");
|
||||
return null;
|
||||
}
|
||||
|
||||
var targets = deploymentTargetManager.GetKnownTargets();
|
||||
var loggers = new Dictionary<string, DeploymentTargetLogger>();
|
||||
|
||||
foreach (var target in targets)
|
||||
{
|
||||
if (target.status != DeploymentTargetStatus.Ready) continue;
|
||||
|
||||
var logger = deploymentTargetManager.GetTargetLogger(target.id);
|
||||
logger.Clear();
|
||||
loggers.Add(target.id, logger);
|
||||
}
|
||||
|
||||
return loggers;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,115 @@
|
||||
using System;
|
||||
using UnityEditor.Networking.PlayerConnection;
|
||||
using UnityEditor.TestTools.TestRunner;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEditor.TestTools.TestRunner.UnityTestProtocol;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking.PlayerConnection;
|
||||
using UnityEngine.TestRunner.TestLaunchers;
|
||||
|
||||
namespace UnityEditor.TestRunner.TestLaunchers
|
||||
{
|
||||
[Serializable]
|
||||
internal class RemoteTestRunController : ScriptableSingleton<RemoteTestRunController>
|
||||
{
|
||||
internal const int k_HeartbeatTimeout = 60 * 10;
|
||||
|
||||
[SerializeField]
|
||||
private RemoteTestResultReciever m_RemoteTestResultReciever;
|
||||
|
||||
[SerializeField]
|
||||
private PlatformSpecificSetup m_PlatformSpecificSetup;
|
||||
|
||||
[SerializeField]
|
||||
private bool m_RegisteredConnectionCallbacks;
|
||||
|
||||
[SerializeField]
|
||||
private int m_HearbeatTimeOut;
|
||||
|
||||
private UnityEditor.TestTools.TestRunner.DelayedCallback m_TimeoutCallback;
|
||||
|
||||
public void Init(BuildTarget buildTarget, int heartbeatTimeout)
|
||||
{
|
||||
m_HearbeatTimeOut = heartbeatTimeout;
|
||||
m_PlatformSpecificSetup = new PlatformSpecificSetup(buildTarget);
|
||||
m_PlatformSpecificSetup.Setup();
|
||||
m_RemoteTestResultReciever = new RemoteTestResultReciever();
|
||||
EditorConnection.instance.Initialize();
|
||||
if (!m_RegisteredConnectionCallbacks)
|
||||
{
|
||||
EditorConnection.instance.Initialize();
|
||||
DelegateEditorConnectionEvents();
|
||||
}
|
||||
}
|
||||
|
||||
private void DelegateEditorConnectionEvents()
|
||||
{
|
||||
m_RegisteredConnectionCallbacks = true;
|
||||
//This is needed because RemoteTestResultReceiver is not a ScriptableObject
|
||||
EditorConnection.instance.Register(PlayerConnectionMessageIds.runStartedMessageId, RunStarted);
|
||||
EditorConnection.instance.Register(PlayerConnectionMessageIds.runFinishedMessageId, RunFinished);
|
||||
EditorConnection.instance.Register(PlayerConnectionMessageIds.testStartedMessageId, TestStarted);
|
||||
EditorConnection.instance.Register(PlayerConnectionMessageIds.testFinishedMessageId, TestFinished);
|
||||
EditorConnection.instance.Register(PlayerConnectionMessageIds.playerAliveHeartbeat, PlayerAliveHeartbeat);
|
||||
}
|
||||
|
||||
private void RunStarted(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
m_TimeoutCallback?.Reset();
|
||||
m_RemoteTestResultReciever.RunStarted(messageEventArgs);
|
||||
CallbacksDelegator.instance.RunStartedRemotely(messageEventArgs.data);
|
||||
}
|
||||
|
||||
private void RunFinished(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
m_TimeoutCallback?.Clear();
|
||||
m_RemoteTestResultReciever.RunFinished(messageEventArgs);
|
||||
m_PlatformSpecificSetup.CleanUp();
|
||||
|
||||
CallbacksDelegator.instance.RunFinishedRemotely(messageEventArgs.data);
|
||||
}
|
||||
|
||||
private void TestStarted(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
m_TimeoutCallback?.Reset();
|
||||
CallbacksDelegator.instance.TestStartedRemotely(messageEventArgs.data);
|
||||
}
|
||||
|
||||
private void TestFinished(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
m_TimeoutCallback?.Reset();
|
||||
CallbacksDelegator.instance.TestFinishedRemotely(messageEventArgs.data);
|
||||
}
|
||||
|
||||
private void PlayerAliveHeartbeat(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
m_TimeoutCallback?.Reset();
|
||||
}
|
||||
|
||||
private void TimeoutCallback()
|
||||
{
|
||||
CallbacksDelegator.instance.RunFailed($"Test execution timed out. No activity received from the player in {m_HearbeatTimeOut} seconds.");
|
||||
}
|
||||
|
||||
public void PostBuildAction()
|
||||
{
|
||||
m_PlatformSpecificSetup.PostBuildAction();
|
||||
}
|
||||
|
||||
public void PostSuccessfulBuildAction()
|
||||
{
|
||||
m_PlatformSpecificSetup.PostSuccessfulBuildAction();
|
||||
m_TimeoutCallback = new UnityEditor.TestTools.TestRunner.DelayedCallback(TimeoutCallback, m_HearbeatTimeOut);
|
||||
}
|
||||
|
||||
public void PostSuccessfulLaunchAction()
|
||||
{
|
||||
m_PlatformSpecificSetup.PostSuccessfulLaunchAction();
|
||||
}
|
||||
|
||||
public void CleanUp()
|
||||
{
|
||||
m_PlatformSpecificSetup.CleanUp();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,22 @@
|
||||
using System;
|
||||
using UnityEditor.Networking.PlayerConnection;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Networking.PlayerConnection;
|
||||
using UnityEngine.TestRunner.TestLaunchers;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
[Serializable]
|
||||
internal class RemoteTestResultReciever
|
||||
{
|
||||
public void RunStarted(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
}
|
||||
|
||||
public void RunFinished(MessageEventArgs messageEventArgs)
|
||||
{
|
||||
EditorConnection.instance.Send(PlayerConnectionMessageIds.quitPlayerMessageId, null, messageEventArgs.playerId);
|
||||
EditorConnection.instance.DisconnectAll();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using UnityEditor.Events;
|
||||
using UnityEditor.SceneManagement;
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using UnityEngine.TestRunner.NUnitExtensions.Runner;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.TestTools.NUnitExtensions;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
using UnityEngine.TestTools.Utils;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal abstract class RuntimeTestLauncherBase : TestLauncherBase
|
||||
{
|
||||
protected Scene CreateBootstrapScene(string sceneName, Action<PlaymodeTestsController> runnerSetup)
|
||||
{
|
||||
var scene = EditorSceneManager.NewScene(NewSceneSetup.EmptyScene, NewSceneMode.Single);
|
||||
var go = new GameObject(PlaymodeTestsController.kPlaymodeTestControllerName);
|
||||
|
||||
var editorLoadedTestAssemblyProvider = new EditorLoadedTestAssemblyProvider(new EditorCompilationInterfaceProxy(), new EditorAssembliesProxy());
|
||||
|
||||
var runner = go.AddComponent<PlaymodeTestsController>();
|
||||
runnerSetup(runner);
|
||||
runner.settings.bootstrapScene = sceneName;
|
||||
runner.AssembliesWithTests = editorLoadedTestAssemblyProvider.GetAssembliesGroupedByType(TestPlatform.PlayMode).Select(x => x.Assembly.GetName().Name).ToList();
|
||||
|
||||
EditorSceneManager.MarkSceneDirty(scene);
|
||||
AssetDatabase.SaveAssets();
|
||||
EditorSceneManager.SaveScene(scene, sceneName, false);
|
||||
|
||||
return scene;
|
||||
}
|
||||
|
||||
public string CreateSceneName()
|
||||
{
|
||||
return "Assets/InitTestScene" + DateTime.Now.Ticks + ".unity";
|
||||
}
|
||||
|
||||
protected UnityTestAssemblyRunner LoadTests(ITestFilter filter)
|
||||
{
|
||||
var editorLoadedTestAssemblyProvider = new EditorLoadedTestAssemblyProvider(new EditorCompilationInterfaceProxy(), new EditorAssembliesProxy());
|
||||
var assembliesWithTests = editorLoadedTestAssemblyProvider.GetAssembliesGroupedByType(TestPlatform.PlayMode).Select(x => x.Assembly.GetName().Name).ToList();
|
||||
|
||||
var nUnitTestAssemblyRunner = new UnityTestAssemblyRunner(new UnityTestAssemblyBuilder(), null);
|
||||
var assemblyProvider = new PlayerTestAssemblyProvider(new AssemblyLoadProxy(), assembliesWithTests);
|
||||
nUnitTestAssemblyRunner.Load(assemblyProvider.GetUserAssemblies().Select(a => a.Assembly).ToArray(), TestPlatform.PlayMode, UnityTestAssemblyBuilder.GetNUnitTestBuilderSettings(TestPlatform.PlayMode));
|
||||
return nUnitTestAssemblyRunner;
|
||||
}
|
||||
|
||||
protected static void ReopenOriginalScene(string originalSceneName)
|
||||
{
|
||||
EditorSceneManager.NewScene(NewSceneSetup.DefaultGameObjects);
|
||||
if (!string.IsNullOrEmpty(originalSceneName))
|
||||
{
|
||||
EditorSceneManager.OpenScene(originalSceneName);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal static class PlaymodeTestsControllerExtensions
|
||||
{
|
||||
internal static T AddEventHandlerMonoBehaviour<T>(this PlaymodeTestsController controller) where T : MonoBehaviour, ITestRunnerListener
|
||||
{
|
||||
var eventHandler = controller.gameObject.AddComponent<T>();
|
||||
SetListeners(controller, eventHandler);
|
||||
return eventHandler;
|
||||
}
|
||||
|
||||
internal static T AddEventHandlerScriptableObject<T>(this PlaymodeTestsController controller) where T : ScriptableObject, ITestRunnerListener
|
||||
{
|
||||
var eventListener = ScriptableObject.CreateInstance<T>();
|
||||
AddEventHandlerScriptableObject(controller, eventListener);
|
||||
return eventListener;
|
||||
}
|
||||
|
||||
internal static void AddEventHandlerScriptableObject(this PlaymodeTestsController controller, ITestRunnerListener obj)
|
||||
{
|
||||
SetListeners(controller, obj);
|
||||
}
|
||||
|
||||
private static void SetListeners(PlaymodeTestsController controller, ITestRunnerListener eventHandler)
|
||||
{
|
||||
UnityEventTools.AddPersistentListener(controller.testStartedEvent, eventHandler.TestStarted);
|
||||
UnityEventTools.AddPersistentListener(controller.testFinishedEvent, eventHandler.TestFinished);
|
||||
UnityEventTools.AddPersistentListener(controller.runStartedEvent, eventHandler.RunStarted);
|
||||
UnityEventTools.AddPersistentListener(controller.runFinishedEvent, eventHandler.RunFinished);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,92 @@
|
||||
using System;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using Unity.Profiling;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Profiling;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.TestTools.Logging;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
internal abstract class TestLauncherBase
|
||||
{
|
||||
public abstract void Run();
|
||||
|
||||
protected virtual RuntimePlatform? TestTargetPlatform
|
||||
{
|
||||
get { return Application.platform; }
|
||||
}
|
||||
|
||||
protected bool ExecutePreBuildSetupMethods(ITest tests, ITestFilter testRunnerFilter)
|
||||
{
|
||||
using (new ProfilerMarker(nameof(ExecutePreBuildSetupMethods)).Auto()) {
|
||||
var attributeFinder = new PrebuildSetupAttributeFinder();
|
||||
var logString = "Executing setup for: {0}";
|
||||
return ExecuteMethods<IPrebuildSetup>(tests, testRunnerFilter, attributeFinder, logString, targetClass => targetClass.Setup(), TestTargetPlatform);
|
||||
}
|
||||
}
|
||||
|
||||
public void ExecutePostBuildCleanupMethods(ITest tests, ITestFilter testRunnerFilter)
|
||||
{
|
||||
using (new ProfilerMarker(nameof(ExecutePostBuildCleanupMethods)).Auto())
|
||||
ExecutePostBuildCleanupMethods(tests, testRunnerFilter, TestTargetPlatform);
|
||||
}
|
||||
|
||||
public static void ExecutePostBuildCleanupMethods(ITest tests, ITestFilter testRunnerFilter, RuntimePlatform? testTargetPlatform)
|
||||
{
|
||||
using (new ProfilerMarker(nameof(ExecutePostBuildCleanupMethods)).Auto()) {
|
||||
var attributeFinder = new PostbuildCleanupAttributeFinder();
|
||||
var logString = "Executing cleanup for: {0}";
|
||||
ExecuteMethods<IPostBuildCleanup>(tests, testRunnerFilter, attributeFinder, logString, targetClass => targetClass.Cleanup(), testTargetPlatform);
|
||||
}
|
||||
}
|
||||
|
||||
private static bool ExecuteMethods<T>(ITest tests, ITestFilter testRunnerFilter, AttributeFinderBase attributeFinder, string logString, Action<T> action, RuntimePlatform? testTargetPlatform)
|
||||
{
|
||||
var exceptionsThrown = false;
|
||||
|
||||
if (testTargetPlatform == null)
|
||||
{
|
||||
Debug.LogError("Could not determine test target platform from build target " + EditorUserBuildSettings.activeBuildTarget);
|
||||
return true;
|
||||
}
|
||||
|
||||
foreach (var targetClassType in attributeFinder.Search(tests, testRunnerFilter, testTargetPlatform.Value))
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetClass = (T)Activator.CreateInstance(targetClassType);
|
||||
|
||||
Debug.LogFormat(logString, targetClassType.FullName);
|
||||
|
||||
using (var logScope = new LogScope())
|
||||
{
|
||||
action(targetClass);
|
||||
|
||||
if (logScope.AnyFailingLogs())
|
||||
{
|
||||
var failingLog = logScope.FailingLogs.First();
|
||||
throw new UnhandledLogMessageException(failingLog);
|
||||
}
|
||||
|
||||
if (logScope.ExpectedLogs.Any())
|
||||
{
|
||||
var expectedLogs = logScope.ExpectedLogs.First();
|
||||
throw new UnexpectedLogMessageException(expectedLogs);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (InvalidCastException) {}
|
||||
catch (Exception e)
|
||||
{
|
||||
Debug.LogException(e);
|
||||
exceptionsThrown = true;
|
||||
}
|
||||
}
|
||||
|
||||
return exceptionsThrown;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,80 @@
|
||||
using System;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using NUnit.Framework.Internal;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestRunner.NUnitExtensions;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner
|
||||
{
|
||||
[Serializable]
|
||||
internal class TestResultSerializer
|
||||
{
|
||||
private static readonly BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public |
|
||||
BindingFlags.Instance | BindingFlags.FlattenHierarchy;
|
||||
|
||||
[SerializeField] public string id;
|
||||
|
||||
[SerializeField] public string fullName;
|
||||
|
||||
[SerializeField] private double duration;
|
||||
|
||||
[SerializeField] private string label;
|
||||
|
||||
[SerializeField] private string message;
|
||||
|
||||
[SerializeField] private string output;
|
||||
|
||||
[SerializeField] private string site;
|
||||
|
||||
[SerializeField] private string stacktrace;
|
||||
|
||||
[SerializeField] private double startTimeAO;
|
||||
|
||||
[SerializeField] private double endTimeAO;
|
||||
|
||||
[SerializeField] private string status;
|
||||
|
||||
[SerializeField] public string uniqueName;
|
||||
|
||||
public static TestResultSerializer MakeFromTestResult(ITestResult result)
|
||||
{
|
||||
var wrapper = new TestResultSerializer();
|
||||
wrapper.id = result.Test.Id;
|
||||
wrapper.fullName = result.FullName;
|
||||
wrapper.status = result.ResultState.Status.ToString();
|
||||
wrapper.label = result.ResultState.Label;
|
||||
wrapper.site = result.ResultState.Site.ToString();
|
||||
wrapper.output = result.Output;
|
||||
wrapper.duration = result.Duration;
|
||||
wrapper.stacktrace = result.StackTrace;
|
||||
wrapper.message = result.Message;
|
||||
wrapper.startTimeAO = result.StartTime.ToOADate();
|
||||
wrapper.endTimeAO = result.EndTime.ToOADate();
|
||||
wrapper.uniqueName = result.Test.GetUniqueName();
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
public void RestoreTestResult(TestResult result)
|
||||
{
|
||||
var resultState = new ResultState((TestStatus)Enum.Parse(typeof(TestStatus), status), label,
|
||||
(FailureSite)Enum.Parse(typeof(FailureSite), site));
|
||||
var baseType = result.GetType().BaseType;
|
||||
baseType.GetField("_resultState", flags).SetValue(result, resultState);
|
||||
baseType.GetField("_output", flags).SetValue(result, new StringBuilder(output));
|
||||
baseType.GetField("_duration", flags).SetValue(result, duration);
|
||||
baseType.GetField("_message", flags).SetValue(result, message);
|
||||
baseType.GetField("_stackTrace", flags).SetValue(result, stacktrace);
|
||||
baseType.GetProperty("StartTime", flags)
|
||||
.SetValue(result, DateTime.FromOADate(startTimeAO), null);
|
||||
baseType.GetProperty("EndTime", flags)
|
||||
.SetValue(result, DateTime.FromOADate(endTimeAO), null);
|
||||
}
|
||||
|
||||
public bool IsPassed()
|
||||
{
|
||||
return status == TestStatus.Passed.ToString();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,86 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using NUnit.Framework.Interfaces;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools.Logging;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal abstract class BuildActionTaskBase<T> : TestTaskBase
|
||||
{
|
||||
private string typeName;
|
||||
internal IAttributeFinder attributeFinder;
|
||||
internal RuntimePlatform targetPlatform = Application.platform;
|
||||
internal Action<string> logAction = Debug.Log;
|
||||
internal Func<ILogScope> logScopeProvider = () => new LogScope();
|
||||
internal Func<Type, object> createInstance = Activator.CreateInstance;
|
||||
|
||||
protected BuildActionTaskBase(IAttributeFinder attributeFinder)
|
||||
{
|
||||
this.attributeFinder = attributeFinder;
|
||||
typeName = typeof(T).Name;
|
||||
}
|
||||
|
||||
protected abstract void Action(T target);
|
||||
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
if (testJobData.testTree == null)
|
||||
{
|
||||
throw new Exception($"Test tree is not available for {GetType().Name}.");
|
||||
}
|
||||
|
||||
var enumerator = ExecuteMethods(testJobData.testTree, testJobData.executionSettings.BuildNUnitFilter());
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
|
||||
protected IEnumerator ExecuteMethods(ITest testTree, ITestFilter testRunnerFilter)
|
||||
{
|
||||
var exceptions = new List<Exception>();
|
||||
|
||||
foreach (var targetClassType in attributeFinder.Search(testTree, testRunnerFilter, targetPlatform))
|
||||
{
|
||||
try
|
||||
{
|
||||
var targetClass = (T) createInstance(targetClassType);
|
||||
|
||||
logAction($"Executing {typeName} for: {targetClassType.FullName}.");
|
||||
|
||||
using (var logScope = logScopeProvider())
|
||||
{
|
||||
Action(targetClass);
|
||||
|
||||
if (logScope.AnyFailingLogs())
|
||||
{
|
||||
var failingLog = logScope.FailingLogs.First();
|
||||
throw new UnhandledLogMessageException(failingLog);
|
||||
}
|
||||
|
||||
if (logScope.ExpectedLogs.Any())
|
||||
{
|
||||
var expectedLogs = logScope.ExpectedLogs.First();
|
||||
throw new UnexpectedLogMessageException(expectedLogs);
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
exceptions.Add(ex);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (exceptions.Count > 0)
|
||||
{
|
||||
throw new AggregateException($"One or more exceptions when executing {typeName}.", exceptions);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,61 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEditor.TestTools.TestRunner.Api;
|
||||
using UnityEngine.TestRunner.NUnitExtensions;
|
||||
using UnityEngine.TestTools;
|
||||
using UnityEngine.TestTools.NUnitExtensions;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal class BuildTestTreeTask : TestTaskBase
|
||||
{
|
||||
private TestPlatform m_TestPlatform;
|
||||
|
||||
public BuildTestTreeTask(TestPlatform testPlatform)
|
||||
{
|
||||
m_TestPlatform = testPlatform;
|
||||
}
|
||||
|
||||
internal IEditorLoadedTestAssemblyProvider m_testAssemblyProvider = new EditorLoadedTestAssemblyProvider(new EditorCompilationInterfaceProxy(), new EditorAssembliesProxy());
|
||||
internal IAsyncTestAssemblyBuilder m_testAssemblyBuilder = new UnityTestAssemblyBuilder();
|
||||
internal ICallbacksDelegator m_CallbacksDelegator = CallbacksDelegator.instance;
|
||||
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
if (testJobData.testTree != null)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
var assembliesEnumerator = m_testAssemblyProvider.GetAssembliesGroupedByTypeAsync(m_TestPlatform);
|
||||
while (assembliesEnumerator.MoveNext())
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
if (assembliesEnumerator.Current == null)
|
||||
{
|
||||
throw new Exception("Assemblies not retrieved.");
|
||||
}
|
||||
|
||||
var assemblies = assembliesEnumerator.Current.Where(pair => m_TestPlatform.IsFlagIncluded(pair.Key)).SelectMany(pair => pair.Value).Select(x => x.Assembly).ToArray();
|
||||
var buildSettings = UnityTestAssemblyBuilder.GetNUnitTestBuilderSettings(m_TestPlatform);
|
||||
var enumerator = m_testAssemblyBuilder.BuildAsync(assemblies, Enumerable.Repeat(m_TestPlatform, assemblies.Length).ToArray(), buildSettings);
|
||||
while (enumerator.MoveNext())
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
|
||||
var testList = enumerator.Current;
|
||||
if (testList== null)
|
||||
{
|
||||
throw new Exception("Test list not retrieved.");
|
||||
}
|
||||
|
||||
testList.ParseForNameDuplicates();
|
||||
testJobData.testTree = testList;
|
||||
m_CallbacksDelegator.TestTreeRebuild(testList);
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,50 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal class CleanupVerificationTask : FileCleanupVerifierTaskBase
|
||||
{
|
||||
private const string k_Indent = " ";
|
||||
|
||||
internal Action<object> logAction = Debug.LogWarning;
|
||||
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
var currentFiles = GetAllFilesInAssetsDirectory();
|
||||
var existingFiles = testJobData.existingFiles;
|
||||
|
||||
if (currentFiles.Length != existingFiles.Length)
|
||||
{
|
||||
var existingFilesHashSet = new HashSet<string>(existingFiles);
|
||||
var newFiles = currentFiles.Where(file => !existingFilesHashSet.Contains(file)).ToArray();
|
||||
LogWarningForFilesIfAny(newFiles);
|
||||
}
|
||||
|
||||
yield return null;
|
||||
}
|
||||
|
||||
private void LogWarningForFilesIfAny(string[] filePaths)
|
||||
{
|
||||
if (filePaths.Length == 0)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var stringWriter = new StringWriter();
|
||||
stringWriter.WriteLine("Files generated by test without cleanup.");
|
||||
stringWriter.WriteLine(k_Indent + "Found {0} new files.", filePaths.Length);
|
||||
|
||||
foreach (var filePath in filePaths)
|
||||
{
|
||||
stringWriter.WriteLine(k_Indent + filePath);
|
||||
}
|
||||
|
||||
logAction(stringWriter.ToString());
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,14 @@
|
||||
using System;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal abstract class FileCleanupVerifierTaskBase : TestTaskBase
|
||||
{
|
||||
internal Func<string[]> GetAllAssetPathsAction = AssetDatabase.GetAllAssetPaths;
|
||||
|
||||
protected string[] GetAllFilesInAssetsDirectory()
|
||||
{
|
||||
return GetAllAssetPathsAction();
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using UnityEngine.TestTools;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal class LegacyEditModeRunTask : TestTaskBase
|
||||
{
|
||||
public LegacyEditModeRunTask() : base(true)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
var testLauncher = new EditModeLauncher(testJobData.executionSettings.filters, TestPlatform.EditMode, testJobData.executionSettings.runSynchronously);
|
||||
testJobData.editModeRunner = testLauncher.m_EditModeRunner;
|
||||
testLauncher.Run();
|
||||
|
||||
while (testJobData.editModeRunner != null && !testJobData.editModeRunner.RunFinished)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,26 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal class LegacyPlayModeRunTask : TestTaskBase
|
||||
{
|
||||
public LegacyPlayModeRunTask() : base(true)
|
||||
{
|
||||
|
||||
}
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
var settings = PlaymodeTestsControllerSettings.CreateRunnerSettings(testJobData.executionSettings.filters.Select(filter => filter.ToRuntimeTestRunnerFilter(testJobData.executionSettings.runSynchronously)).ToArray());
|
||||
var launcher = new PlaymodeLauncher(settings);
|
||||
|
||||
launcher.Run();
|
||||
|
||||
while (PlaymodeLauncher.IsRunning)
|
||||
{
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,18 @@
|
||||
using System.Collections;
|
||||
using System.Linq;
|
||||
using UnityEngine.TestTools.TestRunner;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal class LegacyPlayerRunTask : TestTaskBase
|
||||
{
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
var executionSettings = testJobData.executionSettings;
|
||||
var settings = PlaymodeTestsControllerSettings.CreateRunnerSettings(executionSettings.filters.Select(filter => filter.ToRuntimeTestRunnerFilter(executionSettings.runSynchronously)).ToArray());
|
||||
var launcher = new PlayerLauncher(settings, executionSettings.targetPlatform, executionSettings.overloadTestRunSettings, executionSettings.playerHeartbeatTimeout);
|
||||
launcher.Run();
|
||||
yield return null;
|
||||
}
|
||||
}
|
||||
}
|
@@ -0,0 +1,39 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
|
||||
namespace UnityEditor.TestTools.TestRunner.TestRun.Tasks
|
||||
{
|
||||
internal class PerformUndoTask : TestTaskBase
|
||||
{
|
||||
private const double warningThreshold = 1000;
|
||||
|
||||
internal Action<int> RevertAllDownToGroup = Undo.RevertAllDownToGroup;
|
||||
internal Action<string> LogWarning = Debug.LogWarning;
|
||||
internal Action<string, string, float> DisplayProgressBar = EditorUtility.DisplayProgressBar;
|
||||
internal Action ClearProgressBar = EditorUtility.ClearProgressBar;
|
||||
internal Func<DateTime> TimeNow = () => DateTime.Now;
|
||||
|
||||
public override IEnumerator Execute(TestJobData testJobData)
|
||||
{
|
||||
if (testJobData.undoGroup < 0)
|
||||
{
|
||||
yield break;
|
||||
}
|
||||
|
||||
DisplayProgressBar("Undo", "Reverting changes to the scene", 0);
|
||||
|
||||
var undoStartTime = TimeNow();
|
||||
|
||||
RevertAllDownToGroup(testJobData.undoGroup);
|
||||
|
||||
var timeDelta = TimeNow() - undoStartTime;
|
||||
if (timeDelta.TotalMilliseconds >= warningThreshold)
|
||||
{
|
||||
LogWarning($"Undo after editor test run took {timeDelta.Seconds} second{(timeDelta.Seconds == 1 ? "" : "s")}.");
|
||||
}
|
||||
|
||||
ClearProgressBar();
|
||||
}
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user