2023-06-21 13:28:15 -04:00
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:
/// <code>
/// var testRunnerApi = ScriptableObject.CreateInstance<TestRunnerApi>();
/// </code>
/// 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 ( null ) ) ;
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 ) ;
}
}
}