You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
284 lines
12 KiB
284 lines
12 KiB
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; |
|
} |
|
} |
|
} |
|
}
|
|
|