diff --git a/com.unity.multiplayer.mlapi/Tests/Runtime/com.unity.multiplayer.mlapi.runtimetests.asmdef b/com.unity.multiplayer.mlapi/Tests/Runtime/com.unity.multiplayer.mlapi.runtimetests.asmdef index da062e7cb9..6f899c67cc 100644 --- a/com.unity.multiplayer.mlapi/Tests/Runtime/com.unity.multiplayer.mlapi.runtimetests.asmdef +++ b/com.unity.multiplayer.mlapi/Tests/Runtime/com.unity.multiplayer.mlapi.runtimetests.asmdef @@ -8,6 +8,8 @@ "optionalUnityReferences": [ "TestAssemblies" ], - "includePlatforms": [], - "excludePlatforms": [] + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "UNITY_EDITOR" + ] } \ No newline at end of file diff --git a/testproject/Assets/Scenes.meta b/testproject/Assets/Scenes.meta new file mode 100644 index 0000000000..3b475ec820 --- /dev/null +++ b/testproject/Assets/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: e6274e6e608eb41f2ace27f3a117474d +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/StreamingAssets.meta b/testproject/Assets/StreamingAssets.meta new file mode 100644 index 0000000000..2926e11ded --- /dev/null +++ b/testproject/Assets/StreamingAssets.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 50c3bcef4ad84445a1f9eb2246d2e172 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/StreamingAssets/empty.txt b/testproject/Assets/StreamingAssets/empty.txt new file mode 100644 index 0000000000..e69de29bb2 diff --git a/testproject/Assets/StreamingAssets/empty.txt.meta b/testproject/Assets/StreamingAssets/empty.txt.meta new file mode 100644 index 0000000000..faaff388a0 --- /dev/null +++ b/testproject/Assets/StreamingAssets/empty.txt.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: ab2fb420f22574b13b33f6913b8736d8 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta new file mode 100644 index 0000000000..6e70f368f6 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f415999c439ee4394bfb822a0fa30051 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta new file mode 100644 index 0000000000..24050f230a --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: b9c9a4d019af9478d9fcebc1ba79d771 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs new file mode 100644 index 0000000000..77f877a115 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; +#if UNITY_EDITOR +using UnityEditor; +using UnityEditor.Build.Reporting; +#endif +using UnityEngine; + +/// +/// This is needed as Unity throws "An abnormal situation has occurred: the PlayerLoop internal function has been called recursively. Please contact Customer Support with a sample project so that we can reproduce the problem and troubleshoot it." +/// when trying to build from Setup() steps in tests. +/// +public static class BuildMultiprocessTestPlayer +{ + public const string MultiprocessBaseMenuName = "MLAPI/Multiprocess Test"; + public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build Test Player #t"; + public const string MainSceneName = "MultiprocessTestScene"; + private static string BuildPathDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds","MultiprocessTests"); + public static string BuildPath => Path.Combine(BuildPathDirectory, "MultiprocessTestPlayer"); + +#if UNITY_EDITOR + [MenuItem(BuildAndExecuteMenuName)] + public static void BuildRelease() + { + var report = BuildPlayer(); + if (report.summary.result != BuildResult.Succeeded) + { + throw new Exception($"Build failed! {report.summary.totalErrors} errors"); + } + } + + [MenuItem(MultiprocessBaseMenuName + "/Build Test Player (Debug)")] + public static void BuildDebug() + { + var report = BuildPlayer(true); + if (report.summary.result != BuildResult.Succeeded) + { + throw new Exception($"Build failed! {report.summary.totalErrors} errors"); + } + } + + [MenuItem(MultiprocessBaseMenuName + "/Delete Test Build")] + public static void DeleteBuild() + { + if (Directory.Exists(BuildPathDirectory)) + { + Directory.Delete(BuildPathDirectory, recursive: true); + } + else + { + Debug.Log($"[{nameof(BuildMultiprocessTestPlayer)}] build directory does not exist ({BuildPathDirectory}) not deleting anything"); + } + } + + /// + /// Needs a separate build than the standalone test builds since we don't want the player to try to connect to the editor to do test + /// reporting. We only want to main node to do that, worker nodes should be dumb + /// + /// + private static BuildReport BuildPlayer(bool isDebug = false) + { + // Save standalone build path to file so we can read it from standalone tests (that are not running from editor) + File.WriteAllText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.BuildInfoFileName), BuildPath); + + // deleting so we don't end up testing on outdated builds if there's a build failure + DeleteBuild(); + + var buildOptions = BuildOptions.None; + buildOptions |= BuildOptions.IncludeTestAssemblies; + buildOptions |= BuildOptions.StrictMode; + if (isDebug) + { + buildOptions |= BuildOptions.Development; + buildOptions |= BuildOptions.AllowDebugging; // enable this if you want to debug your players. Your players + // will have more connection permission popups when launching though + } + + var buildPathToUse = BuildPath; + if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor) + { + buildPathToUse += ".exe"; + } + Debug.Log($"Starting multiprocess player build using path {buildPathToUse}"); + + buildOptions &= ~BuildOptions.AutoRunPlayer; + var buildReport = BuildPipeline.BuildPlayer( + new[] { $"Assets/Scenes/{MainSceneName}.unity" }, + buildPathToUse, + EditorUserBuildSettings.activeBuildTarget, + buildOptions); + + Debug.Log("Build finished"); + return buildReport; + } +#endif +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs.meta new file mode 100644 index 0000000000..87acf05da8 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b389565fd8544431db4c24940cb569c6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs new file mode 100644 index 0000000000..b09ee09914 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -0,0 +1,63 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using UnityEngine; +using Debug = UnityEngine.Debug; + +public class MultiprocessOrchestration +{ + public const string BuildInfoFileName = "buildInfo.txt"; + public const string IsWorkerArg = "-isWorker"; + + public static void StartWorkerNode() + { + var workerProcess = new Process(); + + //TODO this should be replaced eventually by proper orchestration for all supported platforms + // Starting new local processes is a solution to help run perf tests locally. CI should have multi machine orchestration to + // run performance tests with more realistic conditions. + string buildInstructions = $"You probably didn't generate your build. Please make sure you build a player using the '{BuildMultiprocessTestPlayer.BuildAndExecuteMenuName}' menu"; + try + { + var buildInfo = File.ReadAllText(Path.Combine(Application.streamingAssetsPath, BuildInfoFileName)); + switch (Application.platform) + { + case RuntimePlatform.OSXPlayer: + case RuntimePlatform.OSXEditor: + workerProcess.StartInfo.FileName = $"{buildInfo}.app/Contents/MacOS/testproject"; + break; + case RuntimePlatform.WindowsPlayer: + case RuntimePlatform.WindowsEditor: + workerProcess.StartInfo.FileName = $"{buildInfo}.exe"; + break; + default: + throw new NotImplementedException($"{nameof(StartWorkerNode)}: Current platform is not supported"); + } + } + catch (FileNotFoundException) + { + Debug.LogError($"Could not find build info file. {buildInstructions}"); + throw; + } + + workerProcess.StartInfo.UseShellExecute = false; + workerProcess.StartInfo.RedirectStandardError = true; + workerProcess.StartInfo.RedirectStandardOutput = true; + workerProcess.StartInfo.Arguments = $"{IsWorkerArg} -popupwindow -screen-width 100 -screen-height 100"; + // workerNode.StartInfo.Arguments += " -deepprofiling"; // enable for deep profiling + try + { + var newProcessStarted = workerProcess.Start(); + if (!newProcessStarted) + { + throw new Exception("Failed to start worker process!"); + } + } + catch (Win32Exception e) + { + Debug.LogError($"Error starting player, {buildInstructions}, {e.Message} {e.Data} {e.ErrorCode}"); + throw; + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs.meta new file mode 100644 index 0000000000..4797a6c5a7 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b59a46cbb2c54f4d977a05103227453 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef index 2aa7afc6f7..abe114e619 100644 --- a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef +++ b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef @@ -1,11 +1,15 @@ { "name": "TestProject.RuntimeTests", "references": [ + "TestProject.ManualTests", "Unity.Multiplayer.MLAPI.Runtime", - "Unity.Multiplayer.MLAPI.RuntimeTests", - "TestProject.ManualTests" + "Unity.Multiplayer.MLAPI.RuntimeTests" ], "optionalUnityReferences": [ "TestAssemblies" + ], + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "UNITY_EDITOR" ] } \ No newline at end of file