PlayerLauncher.cs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280
  1. using System;
  2. using System.Collections.Generic;
  3. using System.IO;
  4. using System.Linq;
  5. using System.Threading;
  6. using NUnit.Framework.Internal.Filters;
  7. using UnityEditor;
  8. using UnityEditor.SceneManagement;
  9. using UnityEditor.TestRunner.TestLaunchers;
  10. using UnityEditor.TestTools.TestRunner.Api;
  11. using UnityEngine;
  12. using UnityEngine.SceneManagement;
  13. using UnityEngine.TestRunner.Utils;
  14. using UnityEngine.TestTools.TestRunner;
  15. using UnityEngine.TestTools.TestRunner.Callbacks;
  16. namespace UnityEditor.TestTools.TestRunner
  17. {
  18. internal class TestLaunchFailedException : Exception
  19. {
  20. public TestLaunchFailedException() {}
  21. public TestLaunchFailedException(string message) : base(message) {}
  22. }
  23. [Serializable]
  24. internal class PlayerLauncher : RuntimeTestLauncherBase
  25. {
  26. private readonly PlaymodeTestsControllerSettings m_Settings;
  27. private readonly BuildTarget m_TargetPlatform;
  28. private ITestRunSettings m_OverloadTestRunSettings;
  29. private string m_SceneName;
  30. private int m_HeartbeatTimeout;
  31. public PlayerLauncher(PlaymodeTestsControllerSettings settings, BuildTarget? targetPlatform, ITestRunSettings overloadTestRunSettings, int heartbeatTimeout)
  32. {
  33. m_Settings = settings;
  34. m_TargetPlatform = targetPlatform ?? EditorUserBuildSettings.activeBuildTarget;
  35. m_OverloadTestRunSettings = overloadTestRunSettings;
  36. m_HeartbeatTimeout = heartbeatTimeout;
  37. }
  38. protected override RuntimePlatform? TestTargetPlatform
  39. {
  40. get { return BuildTargetConverter.TryConvertToRuntimePlatform(m_TargetPlatform); }
  41. }
  42. public override void Run()
  43. {
  44. var editorConnectionTestCollector = RemoteTestRunController.instance;
  45. editorConnectionTestCollector.hideFlags = HideFlags.HideAndDontSave;
  46. editorConnectionTestCollector.Init(m_TargetPlatform, m_HeartbeatTimeout);
  47. var remotePlayerLogController = RemotePlayerLogController.instance;
  48. remotePlayerLogController.hideFlags = HideFlags.HideAndDontSave;
  49. using (var settings = new PlayerLauncherContextSettings(m_OverloadTestRunSettings))
  50. {
  51. m_SceneName = CreateSceneName();
  52. var scene = PrepareScene(m_SceneName);
  53. string scenePath = scene.path;
  54. var filter = m_Settings.BuildNUnitFilter();
  55. var runner = LoadTests(filter);
  56. var exceptionThrown = ExecutePreBuildSetupMethods(runner.LoadedTest, filter);
  57. if (exceptionThrown)
  58. {
  59. ReopenOriginalScene(m_Settings.originalScene);
  60. AssetDatabase.DeleteAsset(m_SceneName);
  61. CallbacksDelegator.instance.RunFailed("Run Failed: One or more errors in a prebuild setup. See the editor log for details.");
  62. return;
  63. }
  64. EditorSceneManager.MarkSceneDirty(scene);
  65. EditorSceneManager.SaveScene(scene);
  66. var playerBuildOptions = GetBuildOptions(scenePath);
  67. var success = BuildAndRunPlayer(playerBuildOptions);
  68. editorConnectionTestCollector.PostBuildAction();
  69. ExecutePostBuildCleanupMethods(runner.LoadedTest, filter);
  70. ReopenOriginalScene(m_Settings.originalScene);
  71. AssetDatabase.DeleteAsset(m_SceneName);
  72. if (!success)
  73. {
  74. editorConnectionTestCollector.CleanUp();
  75. ScriptableObject.DestroyImmediate(editorConnectionTestCollector);
  76. Debug.LogError("Player build failed");
  77. throw new TestLaunchFailedException("Player build failed");
  78. }
  79. if ((playerBuildOptions.BuildPlayerOptions.options & BuildOptions.AutoRunPlayer) != 0)
  80. {
  81. editorConnectionTestCollector.PostSuccessfulBuildAction();
  82. editorConnectionTestCollector.PostSuccessfulLaunchAction();
  83. }
  84. var runSettings = m_OverloadTestRunSettings as PlayerLauncherTestRunSettings;
  85. if (success && runSettings != null && runSettings.buildOnly)
  86. {
  87. EditorUtility.RevealInFinder(playerBuildOptions.BuildPlayerOptions.locationPathName);
  88. }
  89. }
  90. }
  91. public Scene PrepareScene(string sceneName)
  92. {
  93. var scene = CreateBootstrapScene(sceneName, runner =>
  94. {
  95. runner.AddEventHandlerMonoBehaviour<PlayModeRunnerCallback>();
  96. runner.settings = m_Settings;
  97. var commandLineArgs = Environment.GetCommandLineArgs();
  98. if (!commandLineArgs.Contains("-doNotReportTestResultsBackToEditor"))
  99. {
  100. runner.AddEventHandlerMonoBehaviour<RemoteTestResultSender>();
  101. }
  102. runner.AddEventHandlerMonoBehaviour<PlayerQuitHandler>();
  103. runner.AddEventHandlerScriptableObject<TestRunCallbackListener>();
  104. });
  105. return scene;
  106. }
  107. private static bool BuildAndRunPlayer(PlayerLauncherBuildOptions buildOptions)
  108. {
  109. Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Building player with following options:\n{0}", buildOptions);
  110. // Android has to be in listen mode to establish player connection
  111. if (buildOptions.BuildPlayerOptions.target == BuildTarget.Android)
  112. {
  113. buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
  114. }
  115. // For now, so does Lumin
  116. if (buildOptions.BuildPlayerOptions.target == BuildTarget.Lumin)
  117. {
  118. buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
  119. }
  120. var result = BuildPipeline.BuildPlayer(buildOptions.BuildPlayerOptions);
  121. if (result.summary.result != Build.Reporting.BuildResult.Succeeded)
  122. Debug.LogError(result.SummarizeErrors());
  123. return result.summary.result == Build.Reporting.BuildResult.Succeeded;
  124. }
  125. internal PlayerLauncherBuildOptions GetBuildOptions(string scenePath)
  126. {
  127. var buildOnly = false;
  128. var runSettings = m_OverloadTestRunSettings as PlayerLauncherTestRunSettings;
  129. if (runSettings != null)
  130. {
  131. buildOnly = runSettings.buildOnly;
  132. }
  133. var buildOptions = new BuildPlayerOptions();
  134. var scenes = new List<string>() { scenePath };
  135. scenes.AddRange(EditorBuildSettings.scenes.Select(x => x.path));
  136. buildOptions.scenes = scenes.ToArray();
  137. buildOptions.options |= BuildOptions.Development | BuildOptions.ConnectToHost | BuildOptions.IncludeTestAssemblies | BuildOptions.StrictMode;
  138. buildOptions.target = m_TargetPlatform;
  139. if (EditorUserBuildSettings.waitForPlayerConnection)
  140. buildOptions.options |= BuildOptions.WaitForPlayerConnection;
  141. if (EditorUserBuildSettings.allowDebugging)
  142. buildOptions.options |= BuildOptions.AllowDebugging;
  143. if (EditorUserBuildSettings.installInBuildFolder)
  144. buildOptions.options |= BuildOptions.InstallInBuildFolder;
  145. else if (!buildOnly)
  146. buildOptions.options |= BuildOptions.AutoRunPlayer;
  147. var buildTargetGroup = EditorUserBuildSettings.activeBuildTargetGroup;
  148. //Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
  149. if (PostprocessBuildPlayer.SupportsLz4Compression(buildTargetGroup, m_TargetPlatform))
  150. {
  151. if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4)
  152. buildOptions.options |= BuildOptions.CompressWithLz4;
  153. else if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4HC)
  154. buildOptions.options |= BuildOptions.CompressWithLz4HC;
  155. }
  156. string buildLocation;
  157. if (buildOnly)
  158. {
  159. buildLocation = buildOptions.locationPathName = runSettings.buildOnlyLocationPath;
  160. }
  161. else
  162. {
  163. var reduceBuildLocationPathLength = false;
  164. //Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
  165. if ((m_TargetPlatform == BuildTarget.WSAPlayer)
  166. #if !UNITY_2021_1_OR_NEWER
  167. || (m_TargetPlatform == BuildTarget.XboxOne)
  168. #endif
  169. )
  170. {
  171. reduceBuildLocationPathLength = true;
  172. }
  173. var uniqueTempPathInProject = FileUtil.GetUniqueTempPathInProject();
  174. var playerDirectoryName = "PlayerWithTests";
  175. //Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
  176. if (reduceBuildLocationPathLength)
  177. {
  178. playerDirectoryName = "PwT";
  179. uniqueTempPathInProject = Path.GetTempFileName();
  180. File.Delete(uniqueTempPathInProject);
  181. Directory.CreateDirectory(uniqueTempPathInProject);
  182. }
  183. var tempPath = Path.GetFullPath(uniqueTempPathInProject);
  184. buildLocation = Path.Combine(tempPath, playerDirectoryName);
  185. // iOS builds create a folder with Xcode project instead of an executable, therefore no executable name is added
  186. if (m_TargetPlatform == BuildTarget.iOS)
  187. {
  188. buildOptions.locationPathName = buildLocation;
  189. }
  190. else
  191. {
  192. string extensionForBuildTarget =
  193. PostprocessBuildPlayer.GetExtensionForBuildTarget(buildTargetGroup, buildOptions.target,
  194. buildOptions.options);
  195. var playerExecutableName = "PlayerWithTests";
  196. if (!string.IsNullOrEmpty(extensionForBuildTarget))
  197. playerExecutableName += $".{extensionForBuildTarget}";
  198. buildOptions.locationPathName = Path.Combine(buildLocation, playerExecutableName);
  199. }
  200. }
  201. return new PlayerLauncherBuildOptions
  202. {
  203. BuildPlayerOptions = ModifyBuildOptions(buildOptions),
  204. PlayerDirectory = buildLocation,
  205. };
  206. }
  207. private BuildPlayerOptions ModifyBuildOptions(BuildPlayerOptions buildOptions)
  208. {
  209. var allAssemblies = AppDomain.CurrentDomain.GetAssemblies()
  210. .Where(x => x.GetReferencedAssemblies().Any(z => z.Name == "UnityEditor.TestRunner")).ToArray();
  211. var attributes = allAssemblies.SelectMany(assembly => assembly.GetCustomAttributes(typeof(TestPlayerBuildModifierAttribute), true).OfType<TestPlayerBuildModifierAttribute>()).ToArray();
  212. var modifiers = attributes.Select(attribute => attribute.ConstructModifier()).ToArray();
  213. foreach (var modifier in modifiers)
  214. {
  215. buildOptions = modifier.ModifyOptions(buildOptions);
  216. }
  217. return buildOptions;
  218. }
  219. private static bool ShouldReduceBuildLocationPathLength(BuildTarget target)
  220. {
  221. switch (target)
  222. {
  223. #if UNITY_2020_2_OR_NEWER
  224. case BuildTarget.GameCoreXboxOne:
  225. case BuildTarget.GameCoreXboxSeries:
  226. #else
  227. case BuildTarget.XboxOne:
  228. #endif
  229. case BuildTarget.WSAPlayer:
  230. return true;
  231. default:
  232. return false;
  233. }
  234. }
  235. }
  236. }