PlayerLauncher.cs 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225
  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. }
  81. }
  82. public Scene PrepareScene(string sceneName)
  83. {
  84. var scene = CreateBootstrapScene(sceneName, runner =>
  85. {
  86. runner.AddEventHandlerMonoBehaviour<PlayModeRunnerCallback>();
  87. runner.settings = m_Settings;
  88. var commandLineArgs = Environment.GetCommandLineArgs();
  89. if (!commandLineArgs.Contains("-doNotReportTestResultsBackToEditor"))
  90. {
  91. runner.AddEventHandlerMonoBehaviour<RemoteTestResultSender>();
  92. }
  93. runner.AddEventHandlerMonoBehaviour<PlayerQuitHandler>();
  94. runner.AddEventHandlerScriptableObject<TestRunCallbackListener>();
  95. });
  96. return scene;
  97. }
  98. private static bool BuildAndRunPlayer(PlayerLauncherBuildOptions buildOptions)
  99. {
  100. Debug.LogFormat(LogType.Log, LogOption.NoStacktrace, null, "Building player with following options:\n{0}", buildOptions);
  101. // Android has to be in listen mode to establish player connection
  102. if (buildOptions.BuildPlayerOptions.target == BuildTarget.Android)
  103. {
  104. buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
  105. }
  106. // For now, so does Lumin
  107. if (buildOptions.BuildPlayerOptions.target == BuildTarget.Lumin)
  108. {
  109. buildOptions.BuildPlayerOptions.options &= ~BuildOptions.ConnectToHost;
  110. }
  111. var result = BuildPipeline.BuildPlayer(buildOptions.BuildPlayerOptions);
  112. if (result.summary.result != Build.Reporting.BuildResult.Succeeded)
  113. Debug.LogError(result.SummarizeErrors());
  114. return result.summary.result == Build.Reporting.BuildResult.Succeeded;
  115. }
  116. private PlayerLauncherBuildOptions GetBuildOptions(string scenePath)
  117. {
  118. var buildOptions = new BuildPlayerOptions();
  119. var reduceBuildLocationPathLength = false;
  120. //Some platforms hit MAX_PATH limits during the build process, in these cases minimize the path length
  121. if ((m_TargetPlatform == BuildTarget.WSAPlayer) || (m_TargetPlatform == BuildTarget.XboxOne))
  122. {
  123. reduceBuildLocationPathLength = true;
  124. }
  125. var scenes = new List<string>() { scenePath };
  126. scenes.AddRange(EditorBuildSettings.scenes.Select(x => x.path));
  127. buildOptions.scenes = scenes.ToArray();
  128. buildOptions.options |= BuildOptions.Development | BuildOptions.ConnectToHost | BuildOptions.IncludeTestAssemblies | BuildOptions.StrictMode;
  129. buildOptions.target = m_TargetPlatform;
  130. if (EditorUserBuildSettings.waitForPlayerConnection)
  131. buildOptions.options |= BuildOptions.WaitForPlayerConnection;
  132. if (EditorUserBuildSettings.allowDebugging)
  133. buildOptions.options |= BuildOptions.AllowDebugging;
  134. if (EditorUserBuildSettings.installInBuildFolder)
  135. buildOptions.options |= BuildOptions.InstallInBuildFolder;
  136. else
  137. buildOptions.options |= BuildOptions.AutoRunPlayer;
  138. var buildTargetGroup = EditorUserBuildSettings.activeBuildTargetGroup;
  139. //Check if Lz4 is supported for the current buildtargetgroup and enable it if need be
  140. if (PostprocessBuildPlayer.SupportsLz4Compression(buildTargetGroup, m_TargetPlatform))
  141. {
  142. if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4)
  143. buildOptions.options |= BuildOptions.CompressWithLz4;
  144. else if (EditorUserBuildSettings.GetCompressionType(buildTargetGroup) == Compression.Lz4HC)
  145. buildOptions.options |= BuildOptions.CompressWithLz4HC;
  146. }
  147. var uniqueTempPathInProject = FileUtil.GetUniqueTempPathInProject();
  148. var playerDirectoryName = reduceBuildLocationPathLength ? "PwT" : "PlayerWithTests";
  149. if (reduceBuildLocationPathLength)
  150. {
  151. uniqueTempPathInProject = Path.GetTempFileName();
  152. File.Delete(uniqueTempPathInProject);
  153. Directory.CreateDirectory(uniqueTempPathInProject);
  154. }
  155. var tempPath = Path.GetFullPath(uniqueTempPathInProject);
  156. var buildLocation = Path.Combine(tempPath, playerDirectoryName);
  157. // iOS builds create a folder with Xcode project instead of an executable, therefore no executable name is added
  158. if (m_TargetPlatform == BuildTarget.iOS)
  159. {
  160. buildOptions.locationPathName = buildLocation;
  161. }
  162. else
  163. {
  164. string extensionForBuildTarget = PostprocessBuildPlayer.GetExtensionForBuildTarget(buildTargetGroup, buildOptions.target, buildOptions.options);
  165. var playerExecutableName = "PlayerWithTests";
  166. playerExecutableName += string.Format(".{0}", extensionForBuildTarget);
  167. buildOptions.locationPathName = Path.Combine(buildLocation, playerExecutableName);
  168. }
  169. return new PlayerLauncherBuildOptions
  170. {
  171. BuildPlayerOptions = ModifyBuildOptions(buildOptions),
  172. PlayerDirectory = buildLocation,
  173. };
  174. }
  175. private BuildPlayerOptions ModifyBuildOptions(BuildPlayerOptions buildOptions)
  176. {
  177. var allAssemblies = AppDomain.CurrentDomain.GetAssemblies()
  178. .Where(x => x.GetReferencedAssemblies().Any(z => z.Name == "UnityEditor.TestRunner")).ToArray();
  179. var attributes = allAssemblies.SelectMany(assembly => assembly.GetCustomAttributes(typeof(TestPlayerBuildModifierAttribute), true).OfType<TestPlayerBuildModifierAttribute>()).ToArray();
  180. var modifiers = attributes.Select(attribute => attribute.ConstructModifier()).ToArray();
  181. foreach (var modifier in modifiers)
  182. {
  183. buildOptions = modifier.ModifyOptions(buildOptions);
  184. }
  185. return buildOptions;
  186. }
  187. }
  188. }