123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302 |
- using System;
- using System.Collections.Generic;
- using System.IO;
- using System.Linq;
- using UnityEditor.Build.Reporting;
- 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;
- using Object = UnityEngine.Object;
-
- namespace UnityEditor.TestTools.TestRunner
- {
- internal class TestLaunchFailedException : Exception
- {
- public TestLaunchFailedException() {}
- public TestLaunchFailedException(string message) : base(message) {}
- }
-
- [Serializable]
- internal class PlayerLauncher : RuntimeTestLauncherBase
- {
- private readonly BuildTarget m_TargetPlatform;
- private ITestRunSettings m_OverloadTestRunSettings;
- private string m_SceneName;
- private Scene m_Scene;
- private int m_HeartbeatTimeout;
- private string m_PlayerWithTestsPath;
- private PlaymodeTestsController m_Runner;
-
- internal PlayerLauncherBuildOptions playerBuildOptions { get; private set; }
-
- public PlayerLauncher(PlaymodeTestsControllerSettings settings, BuildTarget? targetPlatform, ITestRunSettings overloadTestRunSettings, int heartbeatTimeout, string playerWithTestsPath, string scenePath, Scene scene, PlaymodeTestsController runner) : base(settings)
- {
- m_TargetPlatform = targetPlatform ?? EditorUserBuildSettings.activeBuildTarget;
- m_OverloadTestRunSettings = overloadTestRunSettings;
- m_HeartbeatTimeout = heartbeatTimeout;
- m_PlayerWithTestsPath = playerWithTestsPath;
- m_SceneName = scenePath;
- m_Scene = scene;
- m_Runner = runner;
- }
-
- 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))
- {
- PrepareScene(m_SceneName, m_Scene, m_Runner);
-
- var filter = m_Settings.BuildNUnitFilter();
- var runner = LoadTests(filter);
- var exceptionThrown = ExecutePreBuildSetupMethods(runner.LoadedTest, filter);
- if (exceptionThrown)
- {
- ReopenOriginalScene(m_Settings.originalScene);
- CallbacksDelegator.instance.RunFailed("Run Failed: One or more errors in a prebuild setup. See the editor log for details.");
- return;
- }
-
- EditorSceneManager.MarkSceneDirty(m_Scene);
- EditorSceneManager.SaveScene(m_Scene);
-
- playerBuildOptions = GetBuildOptions(m_SceneName);
-
- var success = BuildAndRunPlayer(playerBuildOptions);
-
- FilePathMetaInfo.TryCreateFile(runner.LoadedTest, playerBuildOptions.BuildPlayerOptions);
- ExecutePostBuildCleanupMethods(runner.LoadedTest, filter);
-
- ReopenOriginalScene(m_Settings.originalScene);
-
- if (!success)
- {
- Object.DestroyImmediate(editorConnectionTestCollector);
- Debug.LogError("Player build failed");
- throw new TestLaunchFailedException("Player build failed");
- }
-
- if ((playerBuildOptions.BuildPlayerOptions.options & BuildOptions.AutoRunPlayer) != 0)
- {
- editorConnectionTestCollector.PostSuccessfulBuildAction();
- }
-
- var runSettings = m_OverloadTestRunSettings as PlayerLauncherTestRunSettings;
- if (success && runSettings != null && runSettings.buildOnly)
- {
- EditorUtility.RevealInFinder(playerBuildOptions.BuildPlayerOptions.locationPathName);
- }
- }
- }
-
- public void PrepareScene(string sceneName, Scene scene, PlaymodeTestsController runner)
- {
- runner.AddEventHandlerMonoBehaviour<PlayModeRunnerCallback>();
- var commandLineArgs = Environment.GetCommandLineArgs();
- if (!commandLineArgs.Contains("-doNotReportTestResultsBackToEditor"))
- {
- runner.AddEventHandlerMonoBehaviour<RemoteTestResultSender>();
- }
- runner.AddEventHandlerMonoBehaviour<PlayerQuitHandler>();
- runner.AddEventHandlerScriptableObject<TestRunCallbackListener>();
-
- EditorSceneManager.MarkSceneDirty(scene);
- AssetDatabase.SaveAssets();
- EditorSceneManager.SaveScene(scene, sceneName, false);
- }
-
- private static bool BuildAndRunPlayer(PlayerLauncherBuildOptions buildOptions)
- {
- Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Building player with following options:\n{0}", buildOptions);
-
- #if !UNITY_2021_2_OR_NEWER
- // Android has to be in listen mode to establish player connection
- // Only flip connect to host if we are older than Unity 2021.2
- if (buildOptions.BuildPlayerOptions.target == BuildTarget.Android)
- {
- buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
- }
- #endif
- // For now, so does Lumin
- #if !UNITY_2022_2_OR_NEWER
- if (buildOptions.BuildPlayerOptions.target == BuildTarget.Lumin)
- {
- buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
- }
- #endif
-
- #if UNITY_2023_2_OR_NEWER
- // WebGL has to be in close on quit mode to ensure that the browser tab is closed when the player finishes running tests
- if (buildOptions.BuildPlayerOptions.target == BuildTarget.WebGL)
- {
- PlayerSettings.WebGL.closeOnQuit = true;
- }
- #endif
-
- var result = BuildPipeline.BuildPlayer(buildOptions.BuildPlayerOptions);
- if (result.summary.result != BuildResult.Succeeded)
- Debug.LogError(result.SummarizeErrors());
-
- #if UNITY_2023_2_OR_NEWER
- // Clean up WebGL close on quit mode
- if (buildOptions.BuildPlayerOptions.target == BuildTarget.WebGL)
- {
- PlayerSettings.WebGL.closeOnQuit = false;
- }
- #endif
-
- return result.summary.result == 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;
- buildOptions.targetGroup = buildTargetGroup;
-
- //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;
- }
- }
- }
- }
|