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