PlayerLauncher.cs 11 KB

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