285 lines
12 KiB
C#
285 lines
12 KiB
C#
|
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;
|
||
|
private string m_PlayerWithTestsPath;
|
||
|
|
||
|
public PlayerLauncher(PlaymodeTestsControllerSettings settings, BuildTarget? targetPlatform, ITestRunSettings overloadTestRunSettings, int heartbeatTimeout, string playerWithTestsPath)
|
||
|
{
|
||
|
m_Settings = settings;
|
||
|
m_TargetPlatform = targetPlatform ?? EditorUserBuildSettings.activeBuildTarget;
|
||
|
m_OverloadTestRunSettings = overloadTestRunSettings;
|
||
|
m_HeartbeatTimeout = heartbeatTimeout;
|
||
|
m_PlayerWithTestsPath = playerWithTestsPath;
|
||
|
}
|
||
|
|
||
|
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 UNITY_2021_2_OR_NEWER
|
||
|
buildOptions.subtarget = EditorUserBuildSettings.GetActiveSubtargetFor(m_TargetPlatform);
|
||
|
#endif
|
||
|
|
||
|
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)
|
||
|
#if !UNITY_2021_1_OR_NEWER
|
||
|
|| (m_TargetPlatform == BuildTarget.XboxOne)
|
||
|
#endif
|
||
|
)
|
||
|
{
|
||
|
reduceBuildLocationPathLength = true;
|
||
|
}
|
||
|
|
||
|
var uniqueTempPathInProject = FileUtil.GetUniqueTempPathInProject();
|
||
|
var playerDirectoryName = "PlayerWithTests";
|
||
|
|
||
|
//Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
|
||
|
if (reduceBuildLocationPathLength)
|
||
|
{
|
||
|
playerDirectoryName = "PwT";
|
||
|
uniqueTempPathInProject = Path.GetTempFileName();
|
||
|
File.Delete(uniqueTempPathInProject);
|
||
|
Directory.CreateDirectory(uniqueTempPathInProject);
|
||
|
}
|
||
|
|
||
|
buildLocation = Path.Combine(string.IsNullOrEmpty(m_PlayerWithTestsPath) ? Path.GetFullPath(uniqueTempPathInProject) : m_PlayerWithTestsPath, 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";
|
||
|
if (!string.IsNullOrEmpty(extensionForBuildTarget))
|
||
|
playerExecutableName += $".{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;
|
||
|
}
|
||
|
|
||
|
private static bool ShouldReduceBuildLocationPathLength(BuildTarget target)
|
||
|
{
|
||
|
switch (target)
|
||
|
{
|
||
|
#if UNITY_2020_2_OR_NEWER
|
||
|
case BuildTarget.GameCoreXboxOne:
|
||
|
case BuildTarget.GameCoreXboxSeries:
|
||
|
#else
|
||
|
case BuildTarget.XboxOne:
|
||
|
#endif
|
||
|
case BuildTarget.WSAPlayer:
|
||
|
return true;
|
||
|
default:
|
||
|
return false;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
}
|