暫無描述
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.

PlayerLauncher.cs 12KB

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