No Description
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

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285
  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 BuildTarget m_TargetPlatform;
  27. private ITestRunSettings m_OverloadTestRunSettings;
  28. private string m_SceneName;
  29. private int m_HeartbeatTimeout;
  30. private string m_PlayerWithTestsPath;
  31. public PlayerLauncher(PlaymodeTestsControllerSettings settings, BuildTarget? targetPlatform, ITestRunSettings overloadTestRunSettings, int heartbeatTimeout, string playerWithTestsPath) : base(settings)
  32. {
  33. m_TargetPlatform = targetPlatform ?? EditorUserBuildSettings.activeBuildTarget;
  34. m_OverloadTestRunSettings = overloadTestRunSettings;
  35. m_HeartbeatTimeout = heartbeatTimeout;
  36. m_PlayerWithTestsPath = playerWithTestsPath;
  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. #if !UNITY_2021_2_OR_NEWER
  111. // Android has to be in listen mode to establish player connection
  112. // Only flip connect to host if we are older than Unity 2021.2
  113. if (buildOptions.BuildPlayerOptions.target == BuildTarget.Android)
  114. {
  115. buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
  116. }
  117. #endif
  118. // For now, so does Lumin
  119. if (buildOptions.BuildPlayerOptions.target == BuildTarget.Lumin)
  120. {
  121. buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
  122. }
  123. var result = BuildPipeline.BuildPlayer(buildOptions.BuildPlayerOptions);
  124. if (result.summary.result != Build.Reporting.BuildResult.Succeeded)
  125. Debug.LogError(result.SummarizeErrors());
  126. return result.summary.result == Build.Reporting.BuildResult.Succeeded;
  127. }
  128. internal PlayerLauncherBuildOptions GetBuildOptions(string scenePath)
  129. {
  130. var buildOnly = false;
  131. var runSettings = m_OverloadTestRunSettings as PlayerLauncherTestRunSettings;
  132. if (runSettings != null)
  133. {
  134. buildOnly = runSettings.buildOnly;
  135. }
  136. var buildOptions = new BuildPlayerOptions();
  137. var scenes = new List<string>() { scenePath };
  138. scenes.AddRange(EditorBuildSettings.scenes.Select(x => x.path));
  139. buildOptions.scenes = scenes.ToArray();
  140. buildOptions.options |= BuildOptions.Development | BuildOptions.ConnectToHost | BuildOptions.IncludeTestAssemblies | BuildOptions.StrictMode;
  141. buildOptions.target = m_TargetPlatform;
  142. #if UNITY_2021_2_OR_NEWER
  143. buildOptions.subtarget = EditorUserBuildSettings.GetActiveSubtargetFor(m_TargetPlatform);
  144. #endif
  145. if (EditorUserBuildSettings.waitForPlayerConnection)
  146. buildOptions.options |= BuildOptions.WaitForPlayerConnection;
  147. if (EditorUserBuildSettings.allowDebugging)
  148. buildOptions.options |= BuildOptions.AllowDebugging;
  149. if (EditorUserBuildSettings.installInBuildFolder)
  150. buildOptions.options |= BuildOptions.InstallInBuildFolder;
  151. else if (!buildOnly)
  152. buildOptions.options |= BuildOptions.AutoRunPlayer;
  153. var buildTargetGroup = EditorUserBuildSettings.activeBuildTargetGroup;
  154. buildOptions.targetGroup = buildTargetGroup;
  155. //Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
  156. if (PostprocessBuildPlayer.SupportsLz4Compression(buildTargetGroup, m_TargetPlatform))
  157. {
  158. if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4)
  159. buildOptions.options |= BuildOptions.CompressWithLz4;
  160. else if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4HC)
  161. buildOptions.options |= BuildOptions.CompressWithLz4HC;
  162. }
  163. string buildLocation;
  164. if (buildOnly)
  165. {
  166. buildLocation = buildOptions.locationPathName = runSettings.buildOnlyLocationPath;
  167. }
  168. else
  169. {
  170. var reduceBuildLocationPathLength = false;
  171. //Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
  172. if ((m_TargetPlatform == BuildTarget.WSAPlayer)
  173. #if !UNITY_2021_1_OR_NEWER
  174. || (m_TargetPlatform == BuildTarget.XboxOne)
  175. #endif
  176. )
  177. {
  178. reduceBuildLocationPathLength = true;
  179. }
  180. var uniqueTempPathInProject = FileUtil.GetUniqueTempPathInProject();
  181. var playerDirectoryName = "PlayerWithTests";
  182. //Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
  183. if (reduceBuildLocationPathLength)
  184. {
  185. playerDirectoryName = "PwT";
  186. uniqueTempPathInProject = Path.GetTempFileName();
  187. File.Delete(uniqueTempPathInProject);
  188. Directory.CreateDirectory(uniqueTempPathInProject);
  189. }
  190. buildLocation = Path.Combine(string.IsNullOrEmpty(m_PlayerWithTestsPath) ? Path.GetFullPath(uniqueTempPathInProject) : m_PlayerWithTestsPath, playerDirectoryName);
  191. // iOS builds create a folder with Xcode project instead of an executable, therefore no executable name is added
  192. if (m_TargetPlatform == BuildTarget.iOS)
  193. {
  194. buildOptions.locationPathName = buildLocation;
  195. }
  196. else
  197. {
  198. string extensionForBuildTarget =
  199. PostprocessBuildPlayer.GetExtensionForBuildTarget(buildTargetGroup, buildOptions.target,
  200. buildOptions.options);
  201. var playerExecutableName = "PlayerWithTests";
  202. if (!string.IsNullOrEmpty(extensionForBuildTarget))
  203. playerExecutableName += $".{extensionForBuildTarget}";
  204. buildOptions.locationPathName = Path.Combine(buildLocation, playerExecutableName);
  205. }
  206. }
  207. return new PlayerLauncherBuildOptions
  208. {
  209. BuildPlayerOptions = ModifyBuildOptions(buildOptions),
  210. PlayerDirectory = buildLocation,
  211. };
  212. }
  213. private BuildPlayerOptions ModifyBuildOptions(BuildPlayerOptions buildOptions)
  214. {
  215. var allAssemblies = AppDomain.CurrentDomain.GetAssemblies()
  216. .Where(x => x.GetReferencedAssemblies().Any(z => z.Name == "UnityEditor.TestRunner")).ToArray();
  217. var attributes = allAssemblies.SelectMany(assembly => assembly.GetCustomAttributes(typeof(TestPlayerBuildModifierAttribute), true).OfType<TestPlayerBuildModifierAttribute>()).ToArray();
  218. var modifiers = attributes.Select(attribute => attribute.ConstructModifier()).ToArray();
  219. foreach (var modifier in modifiers)
  220. {
  221. buildOptions = modifier.ModifyOptions(buildOptions);
  222. }
  223. return buildOptions;
  224. }
  225. private static bool ShouldReduceBuildLocationPathLength(BuildTarget target)
  226. {
  227. switch (target)
  228. {
  229. #if UNITY_2020_2_OR_NEWER
  230. case BuildTarget.GameCoreXboxOne:
  231. case BuildTarget.GameCoreXboxSeries:
  232. #else
  233. case BuildTarget.XboxOne:
  234. #endif
  235. case BuildTarget.WSAPlayer:
  236. return true;
  237. default:
  238. return false;
  239. }
  240. }
  241. }
  242. }