暂无描述
您最多选择25个主题 主题必须以字母或数字开头,可以包含连字符 (-),并且长度不得超过35个字符

PlayerLauncher.cs 13KB

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