From 6d185e5d24a9836b164a0ddd6a7f79775bd3f69e Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 15:58:36 -0400 Subject: [PATCH 01/66] moving orchestration to this branch from test/multiprocess-testing/wip --- ...nity.multiplayer.mlapi.runtimetests.asmdef | 20 ++- testproject/.gitignore | 3 + testproject/Assets/StreamingAssets.meta | 8 ++ testproject/Assets/StreamingAssets/empty.txt | 0 .../Assets/StreamingAssets/empty.txt.meta | 7 ++ .../Helpers/BuildMultiprocessTestPlayer.cs | 118 ++++++++++++++++++ .../BuildMultiprocessTestPlayer.cs.meta | 11 ++ .../Helpers/MultiprocessOrchestration.cs | 61 +++++++++ .../Helpers/MultiprocessOrchestration.cs.meta | 11 ++ .../Runtime/testproject.runtimetests.asmdef | 22 +++- 10 files changed, 253 insertions(+), 8 deletions(-) create mode 100644 testproject/Assets/StreamingAssets.meta create mode 100644 testproject/Assets/StreamingAssets/empty.txt create mode 100644 testproject/Assets/StreamingAssets/empty.txt.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs.meta 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 efbf850de3..07a21b8196 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 @@ -1,12 +1,24 @@ { "name": "Unity.Multiplayer.MLAPI.RuntimeTests", + "rootNamespace": "", "references": [ "Unity.Multiplayer.MLAPI.Runtime", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", "Unity.Multiplayer.MLAPI.Editor" ], - "optionalUnityReferences": [ - "TestAssemblies" - ], "includePlatforms": [], - "excludePlatforms": [] + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "UNITY_EDITOR" + ], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file diff --git a/testproject/.gitignore b/testproject/.gitignore index 72c27e4fe2..933758634d 100644 --- a/testproject/.gitignore +++ b/testproject/.gitignore @@ -69,3 +69,6 @@ crashlytics-build.properties # Temporary auto-generated Android Assets /[Aa]ssets/[Ss]treamingAssets/aa.meta /[Aa]ssets/[Ss]treamingAssets/aa/* + +/[Aa]ssets/[Ss]treamingAssets/buildInfo.txt +/[Aa]ssets/[Ss]treamingAssets/buildInfo.txt.meta 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/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs new file mode 100644 index 0000000000..b7917a266b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -0,0 +1,118 @@ +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 class BuildMultiprocessTestPlayer : MonoBehaviour +{ + public const string multiprocessBaseMenuName = "MLAPI Multiprocess Test"; + public const string BuildAndExecuteMenuName = multiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; + public static string buildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); + public const string mainSceneName = "MultiprocessTestingScene"; + + +#if UNITY_EDITOR + [MenuItem(multiprocessBaseMenuName+"/Build Test Player #t")] + public static void BuildNoExecute() + { + var success = Build(); + if (!success) + { + throw new Exception("Build failed!"); + } + } + + [MenuItem(multiprocessBaseMenuName+"/Build Test Player in debug mode")] + public static void BuildDebug() + { + var success = Build(true); + if (!success) + { + throw new Exception("Build failed!"); + } + } + + [MenuItem(multiprocessBaseMenuName+"/Delete Test Build")] + public static void DeleteBuild() + { + switch (Application.platform) + { + case RuntimePlatform.WindowsPlayer: + case RuntimePlatform.WindowsEditor: + var exePath = $"{buildPath}.exe"; + if (File.Exists(exePath)) + { + File.Delete(exePath); + } + else + { + Debug.Log($"exe {exePath} doesn't exists"); + } + break; + case RuntimePlatform.OSXPlayer: + case RuntimePlatform.OSXEditor: + var toDelete = buildPath + ".app"; + if (Directory.Exists(toDelete)) + { + Directory.Delete(toDelete, recursive: true); + } + else + { + Debug.Log($"directory {toDelete} doesn't exists"); + } + break; + default: + throw new NotImplementedException(); + } + } + + /// + /// 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 + /// + /// + public static bool Build(bool isDebug = false) + { + // Save standalone build path to file + var f = File.CreateText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.buildInfoFileName)); + f.Write(buildPath); + f.Close(); + + // var buildPath = Application.streamingAssetsPath; + // 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"; + } + + buildOptions &= ~BuildOptions.AutoRunPlayer; + var buildReport = BuildPipeline.BuildPlayer( + new[] { $"Assets/Scenes/{mainSceneName}.unity" }, + buildPathToUse, + EditorUserBuildSettings.activeBuildTarget, + buildOptions); + + return buildReport.summary.result == BuildResult.Succeeded; + } +#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..7f9f84d82c --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -0,0 +1,61 @@ +using System; +using System.ComponentModel; +using System.Diagnostics; +using System.IO; +using UnityEditor; +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 workerNode = new Process(); + + //TODO this should be replaced eventually by proper orchestration for all supported platforms + 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: + workerNode.StartInfo.FileName = $"{buildInfo}.app/Contents/MacOS/testproject"; + break; + case RuntimePlatform.WindowsPlayer: + case RuntimePlatform.WindowsEditor: + workerNode.StartInfo.FileName = $"{buildInfo}.exe"; + break; + default: + throw new NotImplementedException("StartWorkerNode: Current platform not supported"); + } + } + catch (FileNotFoundException) + { + throw new Exception($"Couldn't find build info file. {buildInstructions}"); + } + + workerNode.StartInfo.UseShellExecute = false; + workerNode.StartInfo.RedirectStandardError = true; + workerNode.StartInfo.RedirectStandardOutput = true; + workerNode.StartInfo.Arguments = $"{isWorkerArg} -popupwindow -screen-width 100 -screen-height 100"; + // workerNode.StartInfo.Arguments += " -deepprofiling"; // enable for deep profiling + try + { + var newProcessStarted = workerNode.Start(); + if (!newProcessStarted) + { + throw new Exception("Process not started!"); + } + } + 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..45c640863c 100644 --- a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef +++ b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef @@ -1,11 +1,25 @@ { "name": "TestProject.RuntimeTests", + "rootNamespace": "", "references": [ "Unity.Multiplayer.MLAPI.Runtime", "Unity.Multiplayer.MLAPI.RuntimeTests", - "TestProject.ManualTests" + "TestProject.ManualTests", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner" ], - "optionalUnityReferences": [ - "TestAssemblies" - ] + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS", + "UNITY_EDITOR" + ], + "versionDefines": [], + "noEngineReferences": false } \ No newline at end of file From 4f6d799560c3d5da95364c835174330a56e426f9 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 16:10:49 -0400 Subject: [PATCH 02/66] moving base for multiprocess tests to this branch from test/multiprocess-testing/wip --- .../Prefabs/PerfTestNetworkObject.prefab | 49 + .../Prefabs/PerfTestNetworkObject.prefab.meta | 7 + .../PerfTestVisualNetworkObject.prefab | 100 ++ .../PerfTestVisualNetworkObject.prefab.meta | 7 + .../Scenes/MultiprocessTestingScene.unity | 922 ++++++++++++++++++ .../MultiprocessTestingScene.unity.meta | 7 + .../BaseMultiprocessTests.cs | 87 ++ .../BaseMultiprocessTests.cs.meta | 11 + .../TestCoordinatorTests.cs | 70 ++ .../TestCoordinatorTests.cs.meta | 11 + ...ltiplayer.mlapi.multiprocessruntime.asmdef | 25 + ...ayer.mlapi.multiprocessruntime.asmdef.meta | 7 + 12 files changed, 1303 insertions(+) create mode 100644 testproject/Assets/Prefabs/PerfTestNetworkObject.prefab create mode 100644 testproject/Assets/Prefabs/PerfTestNetworkObject.prefab.meta create mode 100644 testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab create mode 100644 testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab.meta create mode 100644 testproject/Assets/Scenes/MultiprocessTestingScene.unity create mode 100644 testproject/Assets/Scenes/MultiprocessTestingScene.unity.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef.meta diff --git a/testproject/Assets/Prefabs/PerfTestNetworkObject.prefab b/testproject/Assets/Prefabs/PerfTestNetworkObject.prefab new file mode 100644 index 0000000000..91de39f222 --- /dev/null +++ b/testproject/Assets/Prefabs/PerfTestNetworkObject.prefab @@ -0,0 +1,49 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &5637023994061915634 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 5637023994061915632} + - component: {fileID: 5637023994061915633} + m_Layer: 0 + m_Name: PerfTestNetworkObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &5637023994061915632 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5637023994061915634} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5637023994061915633 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 5637023994061915634} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 951099334 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 diff --git a/testproject/Assets/Prefabs/PerfTestNetworkObject.prefab.meta b/testproject/Assets/Prefabs/PerfTestNetworkObject.prefab.meta new file mode 100644 index 0000000000..feebc7c48c --- /dev/null +++ b/testproject/Assets/Prefabs/PerfTestNetworkObject.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b0952a471c5a147cb92f6afcdb648f8a +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab b/testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab new file mode 100644 index 0000000000..6673fbefcd --- /dev/null +++ b/testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab @@ -0,0 +1,100 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &9115731988109684252 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 9115731988109684241} + - component: {fileID: 9115731988109684240} + - component: {fileID: 9115731988109684243} + - component: {fileID: 9115731988109684253} + m_Layer: 0 + m_Name: PerfTestVisualNetworkObject + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &9115731988109684241 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9115731988109684252} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!33 &9115731988109684240 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9115731988109684252} + m_Mesh: {fileID: 10202, guid: 0000000000000000e000000000000000, type: 0} +--- !u!23 &9115731988109684243 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9115731988109684252} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_StaticShadowCaster: 0 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!114 &9115731988109684253 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9115731988109684252} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 951099334 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 diff --git a/testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab.meta b/testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab.meta new file mode 100644 index 0000000000..f268093417 --- /dev/null +++ b/testproject/Assets/Prefabs/PerfTestVisualNetworkObject.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: c2851feb7276442cc86a6f2d1d69ea11 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Scenes/MultiprocessTestingScene.unity b/testproject/Assets/Scenes/MultiprocessTestingScene.unity new file mode 100644 index 0000000000..65e0b7ee89 --- /dev/null +++ b/testproject/Assets/Scenes/MultiprocessTestingScene.unity @@ -0,0 +1,922 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!29 &1 +OcclusionCullingSettings: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_OcclusionBakeSettings: + smallestOccluder: 5 + smallestHole: 0.25 + backfaceThreshold: 100 + m_SceneGUID: 00000000000000000000000000000000 + m_OcclusionCullingData: {fileID: 0} +--- !u!104 &2 +RenderSettings: + m_ObjectHideFlags: 0 + serializedVersion: 9 + m_Fog: 0 + m_FogColor: {r: 0.5, g: 0.5, b: 0.5, a: 1} + m_FogMode: 3 + m_FogDensity: 0.01 + m_LinearFogStart: 0 + m_LinearFogEnd: 300 + m_AmbientSkyColor: {r: 0.212, g: 0.227, b: 0.259, a: 1} + m_AmbientEquatorColor: {r: 0.114, g: 0.125, b: 0.133, a: 1} + m_AmbientGroundColor: {r: 0.047, g: 0.043, b: 0.035, a: 1} + m_AmbientIntensity: 1 + m_AmbientMode: 0 + m_SubtractiveShadowColor: {r: 0.42, g: 0.478, b: 0.627, a: 1} + m_SkyboxMaterial: {fileID: 10304, guid: 0000000000000000f000000000000000, type: 0} + m_HaloStrength: 0.5 + m_FlareStrength: 1 + m_FlareFadeSpeed: 3 + m_HaloTexture: {fileID: 0} + m_SpotCookie: {fileID: 10001, guid: 0000000000000000e000000000000000, type: 0} + m_DefaultReflectionMode: 0 + m_DefaultReflectionResolution: 128 + m_ReflectionBounces: 1 + m_ReflectionIntensity: 1 + m_CustomReflection: {fileID: 0} + m_Sun: {fileID: 0} + m_IndirectSpecularColor: {r: 0.44657874, g: 0.49641275, b: 0.5748172, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 12 + m_GIWorkflowMode: 1 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_LightmapEditorSettings: + serializedVersion: 12 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAmbientOcclusion: 0 + m_Padding: 2 + m_LightmapParameters: {fileID: 0} + m_LightmapsBakeMode: 1 + m_TextureCompression: 1 + m_FinalGather: 0 + m_FinalGatherFiltering: 1 + m_FinalGatherRayCount: 256 + m_ReflectionCompression: 2 + m_MixedBakeMode: 2 + m_BakeBackend: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVRBounces: 2 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVREnvironmentMIS: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_LightProbeSampleCountMultiplier: 4 + m_LightingDataAsset: {fileID: 0} + m_LightingSettings: {fileID: 130932425} +--- !u!196 &4 +NavMeshSettings: + serializedVersion: 2 + m_ObjectHideFlags: 0 + m_BuildSettings: + serializedVersion: 2 + agentTypeID: 0 + agentRadius: 0.5 + agentHeight: 2 + agentSlope: 45 + agentClimb: 0.4 + ledgeDropHeight: 0 + maxJumpAcrossDistance: 0 + minRegionArea: 2 + manualCellSize: 0 + cellSize: 0.16666667 + manualTileSize: 0 + tileSize: 256 + accuratePlacement: 0 + maxJobWorkers: 0 + preserveTilesOutsideBounds: 0 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &127222500 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 127222502} + - component: {fileID: 127222501} + m_Layer: 0 + m_Name: Directional Light + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!108 &127222501 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 127222500} + m_Enabled: 1 + serializedVersion: 10 + m_Type: 1 + m_Shape: 0 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + m_InnerSpotAngle: 21.80208 + m_CookieSize: 10 + m_Shadows: + m_Type: 2 + m_Resolution: -1 + m_CustomResolution: -1 + m_Strength: 1 + m_Bias: 0.05 + m_NormalBias: 0.4 + m_NearPlane: 0.2 + m_CullingMatrixOverride: + e00: 1 + e01: 0 + e02: 0 + e03: 0 + e10: 0 + e11: 1 + e12: 0 + e13: 0 + e20: 0 + e21: 0 + e22: 1 + e23: 0 + e30: 0 + e31: 0 + e32: 0 + e33: 1 + m_UseCullingMatrixOverride: 0 + m_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingLayerMask: 1 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_BoundingSphereOverride: {x: 0, y: 0, z: 0, w: 0} + m_UseBoundingSphereOverride: 0 + m_UseViewFrustumForShadowCasterCull: 1 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &127222502 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 127222500} + m_LocalRotation: {x: 0.40821788, y: -0.23456968, z: 0.10938163, w: 0.8754261} + m_LocalPosition: {x: 0, y: 3, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 50, y: -30, z: 0} +--- !u!850595691 &130932425 +LightingSettings: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_Name: + serializedVersion: 3 + m_GIWorkflowMode: 1 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 0 + m_RealtimeEnvironmentLighting: 1 + m_BounceScale: 1 + m_AlbedoBoost: 1 + m_IndirectOutputScale: 1 + m_UsingShadowmask: 1 + m_BakeBackend: 1 + m_LightmapMaxSize: 1024 + m_BakeResolution: 40 + m_Padding: 2 + m_TextureCompression: 1 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 0 + m_ExtractAO: 0 + m_MixedBakeMode: 2 + m_LightmapsBakeMode: 1 + m_FilterMode: 1 + m_LightmapParameters: {fileID: 15204, guid: 0000000000000000f000000000000000, type: 0} + m_ExportTrainingData: 0 + m_TrainingDataDestination: TrainingData + m_RealtimeResolution: 2 + m_ForceWhiteAlbedo: 0 + m_ForceUpdates: 0 + m_FinalGather: 0 + m_FinalGatherRayCount: 256 + m_FinalGatherFiltering: 1 + m_PVRCulling: 1 + m_PVRSampling: 1 + m_PVRDirectSampleCount: 32 + m_PVRSampleCount: 512 + m_PVREnvironmentSampleCount: 256 + m_PVREnvironmentReferencePointCount: 2048 + m_LightProbeSampleCountMultiplier: 4 + m_PVRBounces: 2 + m_PVRMinBounces: 1 + m_PVREnvironmentMIS: 1 + m_PVRFilteringMode: 1 + m_PVRDenoiserTypeDirect: 1 + m_PVRDenoiserTypeIndirect: 1 + m_PVRDenoiserTypeAO: 1 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 +--- !u!1 &160940364 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 160940368} + - component: {fileID: 160940367} + - component: {fileID: 160940366} + - component: {fileID: 160940365} + m_Layer: 0 + m_Name: Boundary bottom left + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!135 &160940365 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 160940364} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &160940366 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 160940364} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &160940367 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 160940364} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &160940368 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 160940364} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: -10, y: -10, z: -10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 5 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &941021721 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 941021724} + - component: {fileID: 941021723} + - component: {fileID: 941021722} + m_Layer: 0 + m_Name: Main Camera + m_TagString: MainCamera + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!81 &941021722 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 941021721} + m_Enabled: 1 +--- !u!20 &941021723 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 941021721} + m_Enabled: 1 + serializedVersion: 2 + m_ClearFlags: 1 + m_BackGroundColor: {r: 0.19215687, g: 0.3019608, b: 0.4745098, a: 0} + m_projectionMatrixMode: 1 + m_GateFitMode: 2 + m_FOVAxisMode: 0 + m_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_FocalLength: 50 + m_NormalizedViewPortRect: + serializedVersion: 2 + x: 0 + y: 0 + width: 1 + height: 1 + near clip plane: 0.3 + far clip plane: 1000 + field of view: 60 + orthographic: 0 + orthographic size: 5 + m_Depth: -1 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_RenderingPath: -1 + m_TargetTexture: {fileID: 0} + m_TargetDisplay: 0 + m_TargetEye: 3 + m_HDR: 1 + m_AllowMSAA: 1 + m_AllowDynamicResolution: 0 + m_ForceIntoRT: 0 + m_OcclusionCulling: 1 + m_StereoConvergence: 10 + m_StereoSeparation: 0.022 +--- !u!4 &941021724 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 941021721} + m_LocalRotation: {x: 0.21736304, y: -0, z: -0, w: 0.97609085} + m_LocalPosition: {x: 0, y: 9.15, z: -27.5} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 25.108, y: 0, z: 0} +--- !u!1 &996484657 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 996484661} + - component: {fileID: 996484660} + - component: {fileID: 996484659} + - component: {fileID: 996484658} + m_Layer: 0 + m_Name: Boundary center + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!135 &996484658 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 996484657} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &996484659 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 996484657} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &996484660 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 996484657} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &996484661 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 996484657} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 4 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1206022453 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1206022457} + - component: {fileID: 1206022456} + - component: {fileID: 1206022455} + - component: {fileID: 1206022454} + m_Layer: 0 + m_Name: Boundary top right + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!135 &1206022454 +SphereCollider: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1206022453} + m_Material: {fileID: 0} + m_IsTrigger: 0 + m_Enabled: 1 + serializedVersion: 2 + m_Radius: 0.5 + m_Center: {x: 0, y: 0, z: 0} +--- !u!23 &1206022455 +MeshRenderer: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1206022453} + m_Enabled: 1 + m_CastShadows: 1 + m_ReceiveShadows: 1 + m_DynamicOccludee: 1 + m_MotionVectors: 1 + m_LightProbeUsage: 1 + m_ReflectionProbeUsage: 1 + m_RayTracingMode: 2 + m_RayTraceProcedural: 0 + m_RenderingLayerMask: 1 + m_RendererPriority: 0 + m_Materials: + - {fileID: 10303, guid: 0000000000000000f000000000000000, type: 0} + m_StaticBatchInfo: + firstSubMesh: 0 + subMeshCount: 0 + m_StaticBatchRoot: {fileID: 0} + m_ProbeAnchor: {fileID: 0} + m_LightProbeVolumeOverride: {fileID: 0} + m_ScaleInLightmap: 1 + m_ReceiveGI: 1 + m_PreserveUVs: 0 + m_IgnoreNormalsForChartDetection: 0 + m_ImportantGI: 0 + m_StitchLightmapSeams: 1 + m_SelectedEditorRenderState: 3 + m_MinimumChartSize: 4 + m_AutoUVMaxDistance: 0.5 + m_AutoUVMaxAngle: 89 + m_LightmapParameters: {fileID: 0} + m_SortingLayerID: 0 + m_SortingLayer: 0 + m_SortingOrder: 0 + m_AdditionalVertexStreams: {fileID: 0} +--- !u!33 &1206022456 +MeshFilter: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1206022453} + m_Mesh: {fileID: 10207, guid: 0000000000000000e000000000000000, type: 0} +--- !u!4 &1206022457 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1206022453} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10, y: 10, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &1211923374 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1211923376} + - component: {fileID: 1211923375} + - component: {fileID: 1211923377} + - component: {fileID: 1211923378} + m_Layer: 0 + m_Name: NetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1211923375 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211923374} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 593a2fe42fa9d37498c96f9a383b6521, type: 3} + m_Name: + m_EditorClassIdentifier: + DontDestroy: 0 + RunInBackground: 1 + LogLevel: 1 + NetworkConfig: + ProtocolVersion: 0 + NetworkTransport: {fileID: 1674777073} + RegisteredScenes: + - MultiprocessTestingScene + - SampleScene + AllowRuntimeSceneChanges: 0 + PlayerPrefab: {fileID: 4700706668509470175, guid: 7eeaaf9e50c0afc4dab93584a54fb0d6, + type: 3} + NetworkPrefabs: + - Override: 0 + Prefab: {fileID: 9115731988109684252, guid: c2851feb7276442cc86a6f2d1d69ea11, + type: 3} + SourcePrefabToOverride: {fileID: 0} + SourceHashToOverride: 0 + OverridingTargetPrefab: {fileID: 0} + ReceiveTickrate: 64 + NetworkTickIntervalSec: 0.05 + MaxReceiveEventsPerTickRate: -1 + EventTickrate: 64 + ClientConnectionBufferTimeout: 10 + ConnectionApproval: 0 + ConnectionData: + EnableTimeResync: 0 + TimeResyncInterval: 30 + EnableNetworkVariable: 1 + EnsureNetworkVariableLengthSafety: 0 + EnableSceneManagement: 1 + ForceSamePrefabs: 1 + RecycleNetworkIds: 1 + NetworkIdRecycleDelay: 120 + RpcHashSize: 0 + LoadSceneTimeOut: 120 + EnableMessageBuffering: 1 + MessageBufferTimeout: 20 + EnableNetworkLogs: 1 +--- !u!4 &1211923376 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211923374} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: + - {fileID: 1674777072} + - {fileID: 2027640072} + m_Father: {fileID: 0} + m_RootOrder: 2 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1211923377 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211923374} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 068bf11ceb1344667af4cc40950f44f4, type: 3} + m_Name: + m_EditorClassIdentifier: + referencedPrefab: {fileID: 5637023994061915634, guid: b0952a471c5a147cb92f6afcdb648f8a, + type: 3} +--- !u!114 &1211923378 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1211923374} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 55d1c75ce242745ac98f7e7aca6d2d19, type: 3} + m_Name: + m_EditorClassIdentifier: +--- !u!1 &1274245423 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1274245425} + - component: {fileID: 1274245424} + - component: {fileID: 1274245426} + m_Layer: 0 + m_Name: TestCoordinator + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &1274245424 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1274245423} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: d5a57f767e5e46a458fc5d3c628d0cbb, type: 3} + m_Name: + m_EditorClassIdentifier: + GlobalObjectIdHash: 2217825759 + AlwaysReplicateAsRoot: 0 + DontDestroyWithOwner: 0 + AutoObjectParentSync: 1 +--- !u!4 &1274245425 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1274245423} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 10, y: 10, z: 10} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 0} + m_RootOrder: 6 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1274245426 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1274245423} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: ef1240e0784f84eadb77fe822e2e03c7, type: 3} + m_Name: + m_EditorClassIdentifier: + isRegistering: 0 + hasRegistered: 0 +--- !u!1 &1674777071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1674777072} + - component: {fileID: 1674777073} + m_Layer: 0 + m_Name: UNET + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1674777072 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1674777071} + m_LocalRotation: {x: -0, y: -0, z: -0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1211923376} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &1674777073 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 1674777071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: b84c2d8dfe509a34fb59e2b81f8e1319, type: 3} + m_Name: + m_EditorClassIdentifier: + MessageBufferSize: 50000 + MaxConnections: 100 + MaxSentMessageQueueSize: 50000 + ConnectAddress: 127.0.0.1 + ConnectPort: 7777 + ServerListenPort: 7777 + ServerWebsocketListenPort: 8887 + SupportWebsocket: 0 + Channels: [] + UseMLAPIRelay: 0 + MLAPIRelayAddress: 184.72.104.138 + MLAPIRelayPort: 8888 + MessageSendMode: 0 +--- !u!1 &2027640071 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 2027640072} + - component: {fileID: 2027640073} + m_Layer: 0 + m_Name: UTP + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &2027640072 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2027640071} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 0, z: 0} + m_LocalScale: {x: 1, y: 1, z: 1} + m_Children: [] + m_Father: {fileID: 1211923376} + m_RootOrder: 1 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &2027640073 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 2027640071} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: fc5ef7b69296d69458910681f29471e6, type: 3} + m_Name: + m_EditorClassIdentifier: + Port: 7777 + Address: 127.0.0.1 diff --git a/testproject/Assets/Scenes/MultiprocessTestingScene.unity.meta b/testproject/Assets/Scenes/MultiprocessTestingScene.unity.meta new file mode 100644 index 0000000000..5a9d45d780 --- /dev/null +++ b/testproject/Assets/Scenes/MultiprocessTestingScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 76743cb7b342c49279327834918a9c6e +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs new file mode 100644 index 0000000000..8babc0bf17 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace MLAPI.MultiprocessRuntimeTests +{ + public class MultiprocessTestsAttribute : CategoryAttribute + { + public const string multiprocessCategoryName = "Multiprocess"; + public MultiprocessTestsAttribute() : base(multiprocessCategoryName){} + } + + [MultiprocessTests] + public abstract class BaseMultiprocessTests + { + + protected virtual bool m_IsPerformanceTest => true; + + private bool ShouldIgnoreTests => m_IsPerformanceTest && Application.isEditor; + + protected abstract int NbWorkers { get; } + + [OneTimeSetUp] + public virtual void SetupTestFixture() + { + if (ShouldIgnoreTests) + { + Assert.Ignore("Ignoring tests that shouldn't run from unity editor. Performance tests should be run from remote test execution on device (this can be ran using the \"run selected tests (your platform)\" button"); + } + + SceneManager.LoadScene(BuildMultiprocessTestPlayer.mainSceneName, LoadSceneMode.Single); + SceneManager.sceneLoaded += OnSceneLoaded; + + for (int i = 0; i < NbWorkers; i++) + { + MultiprocessOrchestration.StartWorkerNode(); // will automatically start built player as clients + } + } + + private static void OnSceneLoaded(Scene scene, LoadSceneMode mode) + { + SceneManager.sceneLoaded -= OnSceneLoaded; + NetworkManager.Singleton.StartHost(); + } + + [UnitySetUp] + public virtual IEnumerator Setup() + { + yield return new WaitUntil(() => NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer); + + var startTime = Time.time; + while (NetworkManager.Singleton.ConnectedClients.Count <= NbWorkers) + { + yield return new WaitForSeconds(0.2f); + + if (Time.time - startTime > TestCoordinator.maxWaitTimeout) + { + throw new Exception($"waiting too long to see clients to connect, got {NetworkManager.Singleton.ConnectedClients.Count - 1} clients, but was expecting {NbWorkers}, failing"); + } + } + TestCoordinator.Instance.KeepAliveClientRpc(); + } + + [TearDown] + public virtual void Teardown() + { + if (!ShouldIgnoreTests) + { + TestCoordinator.Instance.TestRunTeardown(); + } + } + + [OneTimeTearDown] + public virtual void TeardownSuite() + { + if (!ShouldIgnoreTests) + { + TestCoordinator.Instance.CloseRemoteClientRpc(); + NetworkManager.Singleton.StopHost(); + } + } + } +} + diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs.meta new file mode 100644 index 0000000000..6b52bd9e90 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f77e60aa394b9419784b6c46618fb553 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs new file mode 100644 index 0000000000..4c22ca1d21 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs @@ -0,0 +1,70 @@ +using System; +using System.Collections; +using System.Linq; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace MLAPI.MultiprocessRuntimeTests +{ + [TestFixture(1)] + [TestFixture(2)] + public class TestCoordinatorTests : BaseMultiprocessTests + { + private int m_NbWorkers; + protected override int NbWorkers => m_NbWorkers; + + protected override bool m_IsPerformanceTest => false; + + public TestCoordinatorTests(int nbWorkers) + { + m_NbWorkers = nbWorkers; + } + + private static void ExecuteSimpleCoordinatorTest() + { + TestCoordinator.Instance.WriteTestResultsServerRpc(float.PositiveInfinity); + } + + private static void ExecuteWithArgs(byte[] args) + { + TestCoordinator.Instance.WriteTestResultsServerRpc(args[0]); + } + + [UnityTest] + public IEnumerator CheckTestCoordinator() + { + // Sanity check for TestCoordinator + // Call the method + TestCoordinator.Instance.InvokeFromMethodActionRpc(ExecuteSimpleCoordinatorTest); + + var nbResults = 0; + for (int i = 0; i < NbWorkers; i++) // wait and test for the two clients + { + yield return new WaitUntil(TestCoordinator.ResultIsSet()); + + var (clientId, result) = TestCoordinator.ConsumeCurrentResult().Take(1).Single(); + Assert.Greater(result, 0f); + nbResults++; + } + Assert.That(nbResults, Is.EqualTo(NbWorkers)); + } + + [UnityTest] + public IEnumerator CheckTestCoordinatorWithArgs() + { + TestCoordinator.Instance.InvokeFromMethodActionRpc(ExecuteWithArgs, 99); + var nbResults = 0; + + for (int i = 0; i < NbWorkers; i++) // wait and test for the two clients + { + yield return new WaitUntil(TestCoordinator.ResultIsSet()); + + var (clientId, result) = TestCoordinator.ConsumeCurrentResult().Take(1).Single(); + Assert.That(result, Is.EqualTo(99)); + nbResults++; + } + Assert.That(nbResults, Is.EqualTo(NbWorkers)); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs.meta new file mode 100644 index 0000000000..104be914d0 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f5e62651568514685a0b50d623fa8a96 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef new file mode 100644 index 0000000000..43f80edd04 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef @@ -0,0 +1,25 @@ +{ + "name": "Unity.Multiplayer.MLAPI.MultiprocessRuntime", + "rootNamespace": "", + "references": [ + "Unity.Multiplayer.MLAPI.Runtime", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "ScriptsForAutomatedTesting", + "Unity.PerformanceTesting", + "MultiprocessTestsHelpers" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef.meta new file mode 100644 index 0000000000..a06b37d08b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/com.unity.multiplayer.mlapi.multiprocessruntime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 66273ab9e01074f7da305fe84e13da47 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From efb083cec872a82a42e5ca51e48c721ea9afced2 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 16:37:21 -0400 Subject: [PATCH 03/66] adding fixed testcoordinator --- .../MultiprocessRuntime/TestCoordinator.cs | 337 ++++++++++++++++++ .../TestCoordinator.cs.meta | 11 + 2 files changed, 348 insertions(+) create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs.meta diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs new file mode 100644 index 0000000000..6602faade7 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -0,0 +1,337 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using MLAPI; +using MLAPI.Configuration; +using MLAPI.Logging; +using MLAPI.Messaging; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using UnityEngine; +using UnityEngine.TestTools; +using Debug = UnityEngine.Debug; + +/// +/// TestCoordinator +/// Used for coordinating multiprocess end to end tests. Used to call RPCs on other nodes and gather results +/// This is needed to coordinate server and client execution steps. The current remote player test runner hardcodes test +/// to run in a bootstrap scene before launching the player and doesn't call each tests individually. There's not opportunity +/// to coordinate test execution between client and server with that model. +/// The only per tests communication already existing is to get the results per test as they are running +/// With this test coordinator, it's not possible to start a main test node with the test runner and have that server start other worker nodes +/// on which to execute client tests. We use MLAPI as both a test framework and as the target of our performance tests. +/// +[RequireComponent(typeof(NetworkObject))] +public class TestCoordinator : NetworkBehaviour +{ + public const int perTestTimeout = 5 * 60; // seconds + + public const float maxWaitTimeout = 20; + private const char k_MethodFullNameSplitChar = '@'; + + + private bool m_ShouldShutdown; + private float m_TimeSinceLastConnected; + private float m_TimeSinceLastKeepAlive; + + public static TestCoordinator Instance; + + private Dictionary> m_TestResultsLocal = new Dictionary>(); // this isn't super efficient, but since it's used for signaling around the tests, shouldn't be too bad + private Dictionary m_ClientIsFinished = new Dictionary(); + + public static Dictionary>.KeyCollection AllClientIdsWithResults => Instance.m_TestResultsLocal.Keys; + public static List AllClientIdExceptMine => NetworkManager.Singleton.ConnectedClients.Keys.ToList().FindAll(client => client != NetworkManager.Singleton.LocalClientId); + + private void Awake() + { + if (Instance != null) + { + Debug.LogError("Multiple test coordinator! destroying self"); + Destroy(gameObject); + return; + } + + Instance = this; + } + + public void Start() + { + bool isClient = Environment.GetCommandLineArgs().Any(value => value == MultiprocessOrchestration.isWorkerArg); + if (isClient) + { + Debug.Log("starting MLAPI client"); + NetworkManager.Singleton.StartClient(); + } + + NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; + + ExecuteStepInContext.InitializeAllSteps(); + } + + public void Update() + { + if (Time.time - m_TimeSinceLastKeepAlive > perTestTimeout) + { + QuitApplication(); + Assert.Fail("Stayed idle too long"); + } + if ((IsServer && NetworkManager.Singleton.IsListening) || (IsClient && NetworkManager.Singleton.IsConnectedClient)) + { + m_TimeSinceLastConnected = Time.time; + } + else if (Time.time - m_TimeSinceLastConnected > maxWaitTimeout || m_ShouldShutdown) + { + // Make sure we don't have zombie processes + Debug.Log($"quitting application, shouldShutdown set to {m_ShouldShutdown}, is listening {NetworkManager.Singleton.IsListening}, is connected client {NetworkManager.Singleton.IsConnectedClient}"); + if (!m_ShouldShutdown) + { + QuitApplication(); + Assert.Fail($"something wrong happened, was not connected for {Time.time - m_TimeSinceLastConnected} seconds"); + } + } + } + + private static void QuitApplication() + { +#if UNITY_EDITOR + UnityEditor.EditorApplication.isPlaying = false; +#else + Application.Quit(); +#endif + } + + public void TestRunTeardown() + { + m_TestResultsLocal.Clear(); + } + + public void OnDestroy() + { + if (NetworkObject != null && NetworkManager != null) + { + NetworkManager.OnClientDisconnectCallback -= OnClientDisconnectCallback; + } + } + + private static void OnClientDisconnectCallback(ulong clientId) + { + if (clientId == NetworkManager.Singleton.ServerClientId || clientId == NetworkManager.Singleton.LocalClientId) + { + // if disconnect callback is for me or for server, quit, we're done here + Debug.Log($"received disconnect from {clientId}, quitting"); + QuitApplication(); + } + } + + private static string GetMethodInfo(Action method) + { + return $"{method.Method.DeclaringType.FullName}{k_MethodFullNameSplitChar}{method.Method.Name}"; + } + + private static string GetMethodInfo(Action method) + { + return $"{method.Method.DeclaringType.FullName}{k_MethodFullNameSplitChar}{method.Method.Name}"; + } + + public static IEnumerable<(ulong clientId, float result)> ConsumeCurrentResult() + { + foreach (var kv in Instance.m_TestResultsLocal) + { + while (kv.Value.Count > 0) + { + var toReturn = (kv.Key, kv.Value[0]); + kv.Value.RemoveAt(0); + yield return toReturn; + } + } + } + + public static IEnumerable ConsumeCurrentResult(ulong clientID) + { + var allResults = Instance.m_TestResultsLocal[clientID]; + while (allResults.Count > 0) + { + var toReturn = allResults[0]; + allResults.RemoveAt(0); + yield return toReturn; + } + } + + public static float PeekLatestResult(ulong clientId) + { + if (Instance.m_TestResultsLocal.ContainsKey(clientId) && Instance.m_TestResultsLocal[clientId].Count > 0) + { + return Instance.m_TestResultsLocal[clientId].Last(); + } + + return float.NaN; + } + + /// + /// Returns appropriate lambda according to parameters + /// Includes time check to make sure this times out + /// + /// + /// + /// + public static Func ResultIsSet(bool useTimeoutException = true) + { + var startWaitTime = Time.time; + return () => + { + if (Time.time - startWaitTime > maxWaitTimeout) + { + if (useTimeoutException) + { + throw new Exception($"timeout while waiting for results, didn't get results for {Time.time - startWaitTime} seconds"); + } + + return true; + } + + foreach (var clientIdAndTestResultList in Instance.m_TestResultsLocal) + { + if (clientIdAndTestResultList.Value.Count > 0) + { + return true; + } + } + + return false; + }; + } + + public static Func ConsumeClientIsFinished(ulong clientId, bool useTimeoutException = true) + { + var startWaitTime = Time.time; + return () => + { + if (Time.time - startWaitTime > maxWaitTimeout) + { + if (useTimeoutException) + { + throw new Exception($"timeout while waiting for client finished, didn't get results for {Time.time - startWaitTime} seconds"); + } + else + { + return true; + } + } + + if (Instance.m_ClientIsFinished.ContainsKey(clientId) && Instance.m_ClientIsFinished[clientId]) + { + Instance.m_ClientIsFinished[clientId] = false; // consume + return true; + } + + return false; + }; + } + + [ServerRpc(RequireOwnership = false)] + public void ClientFinishedServerRpc(ServerRpcParams p = default) + { + // signal from clients to the server to say the client is done with it's task + m_ClientIsFinished[p.Receive.SenderClientId] = true; + } + + public void InvokeFromMethodActionRpc(Action methodInfo, params byte[] args) + { + var methodInfoString = GetMethodInfo(methodInfo); + InvokeFromMethodNameClientRpc(methodInfoString, args, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = AllClientIdExceptMine.ToArray() } }); + } + + public void InvokeFromMethodActionRpc(Action methodInfo) + { + var methodInfoString = GetMethodInfo(methodInfo); + InvokeFromMethodNameClientRpc(methodInfoString, null, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = AllClientIdExceptMine.ToArray() } }); + } + + [ClientRpc] + public void TriggerActionIDClientRpc(string actionID, byte[] args, ClientRpcParams clientRpcParams = default) + { + Debug.Log($"received RPC from server, client side triggering action ID {actionID}"); + try + { + ExecuteStepInContext.allActions[actionID].Invoke(args); + } + catch (Exception e) + { + WriteErrorServerRpc(e.Message); + throw; + } + } + + [ClientRpc] + public void InvokeFromMethodNameClientRpc(string methodInfoString, byte[] args, ClientRpcParams clientRpcParams = default) + { + try + { + var split = methodInfoString.Split(k_MethodFullNameSplitChar); + var (classToExecute, staticMethodToExecute) = (split[0], split[1]); + + var foundType = Type.GetType(classToExecute); + if (foundType == null) + { + throw new Exception($"couldn't find {classToExecute}"); + } + + var foundMethod = foundType.GetMethod(staticMethodToExecute, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); + if (foundMethod == null) + { + throw new Exception($"couldn't find method {staticMethodToExecute}"); + } + + foundMethod.Invoke(null, args != null ? new object[] { args } : null); + } + catch (Exception e) + { + WriteErrorServerRpc(e.Message); + throw; + } + } + + [ClientRpc] + public void CloseRemoteClientRpc() + { + try + { + NetworkManager.Singleton.StopClient(); + m_ShouldShutdown = true; // wait until isConnectedClient is false to run Application Quit in next update + Debug.Log("Quitting player cleanly"); + Application.Quit(); + } + catch (Exception e) + { + WriteErrorServerRpc(e.Message); + throw; + } + } + + [ClientRpc] + public void KeepAliveClientRpc() + { + m_TimeSinceLastKeepAlive = Time.time; + } + + [ServerRpc(RequireOwnership = false)] + public void WriteTestResultsServerRpc(float result, ServerRpcParams receiveParams = default) + { + var senderId = receiveParams.Receive.SenderClientId; + if (!m_TestResultsLocal.ContainsKey(senderId)) + { + m_TestResultsLocal[senderId] = new List(); + } + + m_TestResultsLocal[senderId].Add(result); + } + + [ServerRpc(RequireOwnership = false)] + public void WriteErrorServerRpc(string errorMessage, ServerRpcParams receiveParams = default) + { + Debug.LogError($"Got Exception client side {errorMessage}, from client {receiveParams.Receive.SenderClientId}"); + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs.meta new file mode 100644 index 0000000000..f8c2a3c472 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef1240e0784f84eadb77fe822e2e03c7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 4ea4d4ca720bb0b733872f4eefd5db950a73561f Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 16:42:25 -0400 Subject: [PATCH 04/66] adding missing change --- testproject/ProjectSettings/EditorBuildSettings.asset | 3 +++ 1 file changed, 3 insertions(+) diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index 0f1a066d92..da26d6d0c7 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -41,4 +41,7 @@ EditorBuildSettings: - enabled: 1 path: Assets/Tests/Manual/NetworkSceneManagerCallbacks/SceneWeAreSwitchingFrom.unity guid: 073bd2111475c0643be45b7abe6a97ad + - enabled: 1 + path: Assets/Scenes/MultiprocessTestingScene.unity + guid: 76743cb7b342c49279327834918a9c6e m_configObjects: {} From ea7cdcf98c2ed4bebe4fdb13dec02f091e32676a Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 16:49:07 -0400 Subject: [PATCH 05/66] moving execute step in context to this branch from test/multiprocess-testing/wip --- .../ExecuteStepInContext.cs | 290 ++++++++++++++++++ .../ExecuteStepInContext.cs.meta | 11 + .../ExecuteStepInContextTests.cs | 226 ++++++++++++++ .../ExecuteStepInContextTests.cs.meta | 11 + .../Helpers/CallbackComponent.cs | 24 ++ .../Helpers/CallbackComponent.cs.meta | 11 + 6 files changed, 573 insertions(+) create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs.meta diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs new file mode 100644 index 0000000000..a13417ab40 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -0,0 +1,290 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using MLAPI; +using MLAPI.Messaging; +using NUnit.Framework; +using NUnit.Framework.Interfaces; +using UnityEngine; +using UnityEngine.TestTools; +using Debug = UnityEngine.Debug; + +/// +/// Allows for context based delegate execution. +/// Can specify where you want that lambda executed (client side? server side?) and it'll automatically wait for the end +/// of a clientRPC server side and vice versa. +/// todo this could be used as an in-game tool too? for protocols that require a lot of back and forth? +/// +public class ExecuteStepInContext : CustomYieldInstruction +{ + public enum StepExecutionContext + { + Server, + Clients + } + + [AttributeUsage(AttributeTargets.Method)] + public class MultiprocessContextBasedTestAttribute : NUnitAttribute, IOuterUnityTestAction + { + public IEnumerator BeforeTest(ITest test) + { + yield return new WaitUntil(() => TestCoordinator.Instance != null && hasRegistered); + } + + public IEnumerator AfterTest(ITest test) + { + yield break; + } + } + + private StepExecutionContext m_ActionContext; + private Action m_StepToExecute; + private string m_CurrentActionID; + + // as a remote worker, I store all available actions so I can execute them when triggered from RPCs + public static Dictionary allActions = new Dictionary(); + private static Dictionary s_MethodIDCounter = new Dictionary(); + + private NetworkManager m_NetworkManager; + private bool m_IsRegistering; + private List> m_RemoteIsFinishedChecks = new List>(); + private Func m_AdditionalIsFinishedWaiter; + + private bool m_WaitMultipleUpdates; + private bool m_IgnoreTimeoutException; + + private float m_StartTime; + private bool isTimingOut => Time.time - m_StartTime > TestCoordinator.maxWaitTimeout; + private bool ShouldExecuteLocally => (m_ActionContext == StepExecutionContext.Server && m_NetworkManager.IsServer) || (m_ActionContext == StepExecutionContext.Clients && !m_NetworkManager.IsServer); + + public static bool isRegistering; + public static bool hasRegistered; + private static List m_AllClientTestInstances = new List(); // to keep an instance for each tests, so captured context in each step is kept + + /// + /// This MUST be called at the beginning of each test in order to use context based steps. + /// Assumes this is called from same callsite as ExecuteInContext (and assumes this is called from IEnumerator). + /// This relies on the name to be unique for each generated IEnumerator state machines + /// + public static void InitContextSteps() + { + var callerMethod = new StackFrame(1).GetMethod(); + var methodIdentifier = GetMethodIdentifier(callerMethod.DeclaringType.FullName); // since this is called from IEnumerator, this should be a generated class + s_MethodIDCounter[methodIdentifier] = 0; + } + + private static string GetMethodIdentifier(string callerTypeName) + { + var info = callerTypeName; + return info; + } + + internal static void InitializeAllSteps() + { + // registering magically all context based steps + isRegistering = true; + var registeredMethods = typeof(TestCoordinator).Assembly.GetTypes().SelectMany(t => t.GetMethods()) + .Where(m => m.GetCustomAttributes(typeof(ExecuteStepInContext.MultiprocessContextBasedTestAttribute), true).Length > 0) + .ToArray(); + HashSet typesWithContextMethods = new HashSet(); + foreach (var method in registeredMethods) + { + typesWithContextMethods.Add(method.ReflectedType); + } + + if (registeredMethods.Length == 0) + { + throw new Exception("Couldn't find any registered methods for multiprocess testing. Is TestCoordinator in same assembly as test methods?"); + } + + object[] GetParameterValuesToPass(ParameterInfo[] parameterInfo) + { + object[] parametersToReturn = new object[parameterInfo.Length]; + for (int i = 0; i < parameterInfo.Length; i++) + { + var paramType = parameterInfo[i].GetType(); + object defaultObj = null; + if (paramType.IsValueType) + { + defaultObj = Activator.CreateInstance(paramType); + } + + parametersToReturn[i] = defaultObj; + } + + return parametersToReturn; + } + + foreach (var contextType in typesWithContextMethods) + { + var allConstructors = contextType.GetConstructors(); + if (allConstructors.Length > 1) + { + throw new NotImplementedException("Case not implemented where test has more than one contructor"); + } + + var instance = Activator.CreateInstance(contextType, allConstructors.Length > 0 ? GetParameterValuesToPass(allConstructors[0].GetParameters()) : null); + m_AllClientTestInstances.Add(instance); // keeping that instance so tests can use captured local attributes + + List typeMethodsWithContextSteps = new List(); + foreach (var method in contextType.GetMethods()) + { + if (method.GetCustomAttributes(typeof(ExecuteStepInContext.MultiprocessContextBasedTestAttribute), true).Length > 0) + { + typeMethodsWithContextSteps.Add(method); + } + } + + foreach (var method in typeMethodsWithContextSteps) + { + var parametersToPass = GetParameterValuesToPass(method.GetParameters()); + var enumerator = (IEnumerator)method.Invoke(instance, parametersToPass.ToArray()); + while (enumerator.MoveNext()) { } + } + } + + isRegistering = false; + hasRegistered = true; + } + + /// + /// Executes an action with the specified context. This allows writing tests all in the same sequential flow, + /// making it more readable. This allows not having to jump between static client methods and test method + /// + /// context to use. for example, should execute client side? server side? + /// action to execute + /// waits for timeout and just finishes step execution silently + /// parameters to pass to action + /// + /// waits multiple frames before allowing the execution to continue. This means ClientFinishedServerRpc must be called manually + /// + public ExecuteStepInContext(StepExecutionContext actionContext, Action stepToExecute, bool ignoreTimeoutException = false, byte[] paramToPass = default, NetworkManager networkManager = null, bool waitMultipleUpdates = false, Func additionalIsFinishedWaiter = null) + { + m_StartTime = Time.time; + m_IsRegistering = isRegistering; + m_ActionContext = actionContext; + m_StepToExecute = stepToExecute; + m_WaitMultipleUpdates = waitMultipleUpdates; + m_IgnoreTimeoutException = ignoreTimeoutException; + if (additionalIsFinishedWaiter != null) + { + m_AdditionalIsFinishedWaiter = additionalIsFinishedWaiter; + + // m_IsFinishedChecks.Add(additionalIsFinishedWaiter); + } + + if (networkManager == null) + { + networkManager = NetworkManager.Singleton; + } + + m_NetworkManager = networkManager; // todo test using this for multiinstance tests too? + + var callerMethod = new StackFrame(1).GetMethod(); // one skip frame for current method + + var methodId = GetMethodIdentifier(callerMethod.DeclaringType.FullName); // assumes called from IEnumerator MoveNext, which should be the case since we're a CustomYieldInstruction + if (!s_MethodIDCounter.ContainsKey(methodId)) + { + s_MethodIDCounter[methodId] = 0; + } + + string currentActionID = $"{methodId}-{s_MethodIDCounter[methodId]++}"; + m_CurrentActionID = currentActionID; + + if (m_IsRegistering) + { + Assert.That(allActions, Does.Not.Contain(currentActionID)); // sanity check + allActions[currentActionID] = this; + } + else + { + if (ShouldExecuteLocally) + { + m_StepToExecute.Invoke(paramToPass); + } + else + { + if (networkManager.IsServer) + { + TestCoordinator.Instance.TriggerActionIDClientRpc(currentActionID, paramToPass, + clientRpcParams: new ClientRpcParams + { + Send = new ClientRpcSendParams { TargetClientIds = TestCoordinator.AllClientIdExceptMine.ToArray() } + }); + foreach (var clientId in TestCoordinator.AllClientIdExceptMine) + { + m_RemoteIsFinishedChecks.Add(TestCoordinator.ConsumeClientIsFinished(clientId)); + } + } + else + { + throw new NotImplementedException(); + } + } + } + } + + public void Invoke(byte[] args) + { + m_StepToExecute.Invoke(args); + if (!m_WaitMultipleUpdates) + { + if (!m_NetworkManager.IsServer) + { + TestCoordinator.Instance.ClientFinishedServerRpc(); + } + else + { + throw new NotImplementedException("todo implement"); + } + } + } + + public override bool keepWaiting + { + get + { + if (isTimingOut) + { + if (m_IgnoreTimeoutException) + { + Debug.LogWarning($"Timeout ignored for action ID {m_CurrentActionID}"); + return false; + } + + throw new Exception($"timeout for Context Step with action ID {m_CurrentActionID}"); + } + + if (m_AdditionalIsFinishedWaiter != null) + { + var isFinished = m_AdditionalIsFinishedWaiter.Invoke(); + if (!isFinished) + { + return true; + } + } + + if (m_IsRegistering || ShouldExecuteLocally || m_RemoteIsFinishedChecks == null) + { + return false; + } + + for (int i = m_RemoteIsFinishedChecks.Count - 1; i >= 0; i--) + { + if (m_RemoteIsFinishedChecks[i].Invoke()) + { + m_RemoteIsFinishedChecks.RemoveAt(i); + } + else + { + return true; + } + } + + return false; + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs.meta new file mode 100644 index 0000000000..2b5f013d83 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ff1efc1d00c64914905497db918aadc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs new file mode 100644 index 0000000000..9ddaf1d0e5 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs @@ -0,0 +1,226 @@ +using System; +using System.Collections; +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; +using static ExecuteStepInContext; + +namespace MLAPI.MultiprocessRuntimeTests +{ + /// + /// Smoke tests for ExecuteStepInContext, to make sure it's working properly before being used in other tests + /// + [TestFixture(1)] + [TestFixture(2)] + public class ExecuteStepInContextTests : BaseMultiprocessTests + { + private int m_NbWorkersToTest; + public ExecuteStepInContextTests(int nbWorkersToTest) + { + m_NbWorkersToTest = nbWorkersToTest; + } + protected override int NbWorkers => m_NbWorkersToTest; + protected override bool m_IsPerformanceTest => false; + + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator TestWithParameters([Values(1, 2, 3)] int a) + { + InitContextSteps(); + + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Assert.Less(a, 4); + Assert.Greater(a, 0); + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + var clientA = BitConverter.ToInt32(bytes, 0); + Assert.True(!NetworkManager.Singleton.IsServer); + Assert.Less(clientA, 4); + Assert.Greater(clientA, 0); + }, paramToPass: BitConverter.GetBytes(a)); + } + + [UnityTest, MultiprocessContextBasedTest] + [TestCase(1, 2, ExpectedResult = null)] + [TestCase(2, 3, ExpectedResult = null)] + [TestCase(3, 4, ExpectedResult = null)] + public IEnumerator TestWithParameters(int a, int b) + { + InitContextSteps(); + + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Assert.Less(a, 4); + Assert.Greater(a, 0); + Assert.Less(b, 5); + Assert.Greater(b, 1); + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + var clientB = BitConverter.ToInt32(bytes, 0); + Assert.True(!NetworkManager.Singleton.IsServer); + Assert.Less(clientB, 5); + Assert.Greater(clientB, 1); + }, paramToPass: BitConverter.GetBytes(b)); + } + + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator TestExceptionClientSide() + { + InitContextSteps(); + + const string exceptionMessageToTest = "This is an exception for TestCoordinator that's expected"; + yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => + { + throw new Exception(exceptionMessageToTest); + }, ignoreTimeoutException: true); + yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => + { + for (int i = 0; i < m_NbWorkersToTest; i++) + { + LogAssert.Expect(LogType.Error, new Regex($".*{exceptionMessageToTest}.*")); + } + }); + + const string exceptionUpdateMessageToTest = "This is an exception for update loop client side that's expected"; + yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => + { + void Update(float __) + { + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + throw new Exception(exceptionUpdateMessageToTest); + } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + + }, ignoreTimeoutException: true); + yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => + { + for (int i = 0; i < m_NbWorkersToTest; i++) + { + LogAssert.Expect(LogType.Error, new Regex($".*{exceptionUpdateMessageToTest}.*")); + } + }); + } + + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator ContextTestWithAdditionalWait() + { + InitContextSteps(); + + const int maxValue = 10; + yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => + { + int count = 0; + + void Update(float __) + { + TestCoordinator.Instance.WriteTestResultsServerRpc(count++); + if (count > maxValue) + { + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + } + } + + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + }, additionalIsFinishedWaiter: () => + { + int nbFinished = 0; + for (int i = 0; i < m_NbWorkersToTest; i++) + { + if (TestCoordinator.PeekLatestResult(TestCoordinator.AllClientIdExceptMine[i]) == maxValue) + { + nbFinished++; + } + } + return nbFinished == m_NbWorkersToTest; + }); + yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => + { + Assert.That(TestCoordinator.AllClientIdExceptMine.Count, Is.EqualTo(m_NbWorkersToTest)); + foreach (var clientID in TestCoordinator.AllClientIdExceptMine) + { + var current = 0; + foreach (var res in TestCoordinator.ConsumeCurrentResult(clientID)) + { + Assert.That(res, Is.EqualTo(current++)); + } + Assert.That(current - 1, Is.EqualTo(maxValue)); + } + }); + } + + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator TestExecuteInContext() + { + InitContextSteps(); + + int stepCountExecuted = 0; + yield return new ExecuteStepInContext(StepExecutionContext.Server, args => + { + stepCountExecuted++; + int count = BitConverter.ToInt32(args, 0); + Assert.That(count, Is.EqualTo(1)); + }, paramToPass: BitConverter.GetBytes(1)); + + yield return new ExecuteStepInContext(StepExecutionContext.Clients, args => + { + int count = BitConverter.ToInt32(args, 0); + Assert.That(count, Is.EqualTo(2)); + TestCoordinator.Instance.WriteTestResultsServerRpc(12345); +#if UNITY_EDITOR + Assert.Fail("Should not be here!! This should only execute on client!!"); +#endif + }, paramToPass: BitConverter.GetBytes(2)); + + yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => + { + stepCountExecuted++; + int resultCountFromWorkers = 0; + foreach (var res in TestCoordinator.ConsumeCurrentResult()) + { + resultCountFromWorkers++; + Assert.AreEqual(12345, res.result); + } + + Assert.That(resultCountFromWorkers, Is.EqualTo(NbWorkers)); + }); + + const int timeToWait = 4; + yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => + { + void Update(float __) + { + if (Time.time > timeToWait) + { + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + TestCoordinator.Instance.WriteTestResultsServerRpc(Time.time); + + TestCoordinator.Instance.ClientFinishedServerRpc(); // since finishOnInvoke is false, we need to do this manually + } + } + + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + }, waitMultipleUpdates: true); // waits multiple frames before allowing the next action to continue. + + yield return new ExecuteStepInContext(StepExecutionContext.Server, args => + { + stepCountExecuted++; + int count = 0; + foreach (var res in TestCoordinator.ConsumeCurrentResult()) + { + count++; + Assert.GreaterOrEqual(res.result, timeToWait); + } + + Assert.Greater(count, 0); + }); + + if (!isRegistering) + { + Assert.AreEqual(3, stepCountExecuted); + } + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs.meta new file mode 100644 index 0000000000..3f89ca9cb5 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80b877babc0ce4b0d8cf31e73216d49a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs new file mode 100644 index 0000000000..80ecc0dcbd --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs @@ -0,0 +1,24 @@ +using System; +using UnityEngine; + +/// +/// Component who's purpose is to expose callbacks to code tests +/// +public class CallbackComponent : MonoBehaviour +{ + public Action OnUpdate; + + // Update is called once per frame + private void Update() + { + try + { + OnUpdate?.Invoke(Time.deltaTime); + } + catch (Exception e) + { + TestCoordinator.Instance.WriteErrorServerRpc(e.Message); + throw; + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs.meta new file mode 100644 index 0000000000..7347e89e7b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CallbackComponent.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 55d1c75ce242745ac98f7e7aca6d2d19 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: From 4add65e387d45a76884b4e209f1b94dbceb45006 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 16:59:07 -0400 Subject: [PATCH 06/66] moving perf tests to this branch from test/multiprocess-testing/wip --- .../Scripts/ScriptsForAutomatedTesting.meta | 8 + .../PrefabReference.cs | 22 ++ .../PrefabReference.cs.meta | 11 + .../ScriptsForAutomatedTesting.asmdef | 17 ++ .../ScriptsForAutomatedTesting.asmdef.meta | 7 + .../Tests/Runtime/MultiprocessRuntime.meta | 8 + .../Runtime/MultiprocessRuntime/Helpers.meta | 8 + .../CustomPrefabSpawnerForPerformanceTests.cs | 49 ++++ ...omPrefabSpawnerForPerformanceTests.cs.meta | 11 + .../Helpers/GameObjectPool.cs | 61 +++++ .../Helpers/GameObjectPool.cs.meta | 3 + .../NetworkVariablePerformanceTests.cs | 241 ++++++++++++++++++ .../NetworkVariablePerformanceTests.cs.meta | 11 + testproject/Packages/manifest.json | 13 +- testproject/Packages/packages-lock.json | 32 ++- 15 files changed, 482 insertions(+), 20 deletions(-) create mode 100644 testproject/Assets/Scripts/ScriptsForAutomatedTesting.meta create mode 100644 testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs create mode 100644 testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs.meta create mode 100644 testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef create mode 100644 testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs.meta diff --git a/testproject/Assets/Scripts/ScriptsForAutomatedTesting.meta b/testproject/Assets/Scripts/ScriptsForAutomatedTesting.meta new file mode 100644 index 0000000000..f36507c5f2 --- /dev/null +++ b/testproject/Assets/Scripts/ScriptsForAutomatedTesting.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4326d9cbbfb4c4d2b87f00ea0afd3a86 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs new file mode 100644 index 0000000000..b03408e505 --- /dev/null +++ b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs @@ -0,0 +1,22 @@ +using UnityEngine; + +/// +/// Serves as access point from code to a prefab +/// +public class PrefabReference : MonoBehaviour +{ + [SerializeField] + public GameObject referencedPrefab; + + public static PrefabReference Instance { get; private set; } + + public void Awake() + { + if (Instance != null) + { + Destroy(gameObject); + return; + } + Instance = this; + } +} diff --git a/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs.meta b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs.meta new file mode 100644 index 0000000000..215eee7140 --- /dev/null +++ b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 068bf11ceb1344667af4cc40950f44f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef new file mode 100644 index 0000000000..4bae6efa89 --- /dev/null +++ b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef @@ -0,0 +1,17 @@ +{ + "name": "ScriptsForAutomatedTesting", + "rootNamespace": "", + "references": [ + "GUID:753245531f1b64a6ea7491ec63a5947c", + "GUID:1491147abca9d7d4bb7105af628b223e" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef.meta b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef.meta new file mode 100644 index 0000000000..b74454fe2b --- /dev/null +++ b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/ScriptsForAutomatedTesting.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: e2e647899563e4876b45d58d842e1e3a +AssemblyDefinitionImporter: + 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..6fa880ab43 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 41b26e96c04424a04bae450d789d5a7f +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..2b9c70c07b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ae0eb1e25098241b182babd91479ea26 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs new file mode 100644 index 0000000000..be034a74ee --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs @@ -0,0 +1,49 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; +using MLAPI.Spawning; + +namespace MLAPI.MultiprocessRuntimeTests +{ + public class CustomPrefabSpawnerForPerformanceTests : INetworkPrefabInstanceHandler, IDisposable where T : NetworkBehaviour + { + private GameObjectPool m_ObjectPool; + private Action m_SetupSpawnedObject; + private Action m_OnRelease; + + public CustomPrefabSpawnerForPerformanceTests(T prefabToSpawn, int maxObjectsToSpawn, Action setupSpawnedObject, Action onRelease) + { + m_ObjectPool = new GameObjectPool(); + m_ObjectPool.Init(maxObjectsToSpawn, prefabToSpawn); + m_SetupSpawnedObject = setupSpawnedObject; + m_OnRelease = onRelease; + } + + public NetworkObject HandleNetworkPrefabSpawn(ulong ownerClientId, Vector3 position, Quaternion rotation) + { + var netBehaviour = m_ObjectPool.Get(); + var networkObject = netBehaviour.NetworkObject; + Transform netTransform = networkObject.transform; + netTransform.position = position; + netTransform.rotation = rotation; + m_SetupSpawnedObject(netBehaviour); + return networkObject; + } + + public void HandleNetworkPrefabDestroy(NetworkObject networkObject) + { + var behaviour = networkObject.gameObject.GetComponent(); // todo expensive, only used in teardown for now, should optimize eventually + m_OnRelease(behaviour); + Transform netTransform = networkObject.transform; + netTransform.position = Vector3.zero; + netTransform.rotation = Quaternion.identity; + m_ObjectPool.Release(behaviour); + } + + public void Dispose() + { + m_ObjectPool.Dispose(); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs.meta new file mode 100644 index 0000000000..e36dcb4466 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: dbe82ff88ee654428b03632ec6ffff07 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs new file mode 100644 index 0000000000..289ce619b0 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace MLAPI.MultiprocessRuntimeTests +{ + /// + /// Have to implement our own pool here for compatibility with Unity 2020LTS + /// This shouldn't be needed if we were supporting only 2021 (and its new Pool) + /// + public class GameObjectPool : IDisposable where T : NetworkBehaviour + { + private List m_AllGameObject; + private Stack m_FreeIndexes; + private Dictionary m_ReverseLookup = new Dictionary(); + + public void Init(int originalCount, T prefabToSpawn) + { + m_AllGameObject = new List(originalCount); + m_FreeIndexes = new Stack(originalCount); + for (int i = 0; i < originalCount; i++) + { + var go = Object.Instantiate(prefabToSpawn); + go.gameObject.SetActive(false); + m_AllGameObject.Add(go); + m_FreeIndexes.Push(i); + m_ReverseLookup[go] = i; + } + } + + public void Dispose() + { + foreach (var gameObject in m_AllGameObject) + { + Object.Destroy(gameObject); + } + m_AllGameObject = null; + m_FreeIndexes = null; + m_ReverseLookup = null; + } + + public T Get() + { + if (m_FreeIndexes.Count == 0) + { + throw new Exception("Pool full!"); + } + var o = m_AllGameObject[m_FreeIndexes.Pop()]; + o.gameObject.SetActive(true); + return o; + } + + public void Release(T toRelease) + { + int index = m_ReverseLookup[toRelease]; + m_FreeIndexes.Push(index); + toRelease.gameObject.SetActive(false); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs.meta new file mode 100644 index 0000000000..5a5f42a8e8 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: dc81fb2a3c7347a3a0b5e98d2ba49ed7 +timeCreated: 1622655847 \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs new file mode 100644 index 0000000000..25292e94d6 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -0,0 +1,241 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using MLAPI.NetworkVariable; +using NUnit.Framework; +using Unity.PerformanceTesting; +using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; +using static ExecuteStepInContext; +using Object = UnityEngine.Object; + +namespace MLAPI.MultiprocessRuntimeTests +{ + public class NetworkVariablePerformanceTests : BaseMultiprocessTests + { + protected override int NbWorkers { get; } = 1; + private const int k_MaxObjectsToSpawn = 10000; + private List m_ServerSpawnedObjects = new List(); + private static GameObjectPool s_ServerObjectPool; + private CustomPrefabSpawnerForPerformanceTests m_ClientPrefabHandler; + private OneNetVar m_PrefabToSpawn; + protected override bool m_IsPerformanceTest => true; + + private class OneNetVar : NetworkBehaviour + { + public static int nbInstances; + public NetworkVariableInt oneInt = new NetworkVariableInt(); + + public void Init() + { + nbInstances++; + if (IsServer) + { + oneInt.Value = 1; + } + } + + public static void Stop() + { + nbInstances--; + } + } + + [OneTimeSetUp] + public override void SetupTestFixture() + { + base.SetupTestFixture(); + SceneManager.sceneLoaded += OnSceneLoadedInitSetupSuite; + } + + private void OnSceneLoadedInitSetupSuite(Scene scene, LoadSceneMode loadSceneMode) + { + InitPrefab(); + s_ServerObjectPool = new GameObjectPool(); + s_ServerObjectPool.Init(k_MaxObjectsToSpawn, m_PrefabToSpawn); + } + + private void InitPrefab() + { + var prefabCopy = Object.Instantiate(PrefabReference.Instance.referencedPrefab); + m_PrefabToSpawn = prefabCopy.AddComponent(); + } + + [UnityTest, Performance, MultiprocessContextBasedTest] + public IEnumerator TestSpawningManyObjects([Values(1, 1000, 2000, 10000)] int nbObjects) + { + InitContextSteps(); + + // if (!TestCoordinator.Instance.isRegistering) + // { + // yield return new WaitForSeconds(20); // uncomment to be able to attach debugger and profiler to running build + // } + + yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => + { + Assert.LessOrEqual(nbObjects, k_MaxObjectsToSpawn); // sanity check + }); + + yield return new ExecuteStepInContext(StepExecutionContext.Clients, stepToExecute: nbObjectsBytes => + { + // setup clients + InitPrefab(); + var targetCount = BitConverter.ToInt32(nbObjectsBytes, 0); + + m_ClientPrefabHandler = new CustomPrefabSpawnerForPerformanceTests(m_PrefabToSpawn, k_MaxObjectsToSpawn, SetupSpawnedObject, StopSpawnedObject); + var hasAddedHandler = NetworkManager.Singleton.PrefabHandler.AddHandler(m_PrefabToSpawn.NetworkObject, m_ClientPrefabHandler); + Assert.That(hasAddedHandler); + + // add client side reporter for later spawn steps + void Update(float deltaTime) + { + var count = OneNetVar.nbInstances; + if (count > 0) + { + TestCoordinator.Instance.WriteTestResultsServerRpc(count); + + if (count >= targetCount) + { + // we got what we want, don't update results any longer + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + } + } + } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + }, paramToPass: BitConverter.GetBytes(nbObjects)); + + yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => + { + // start test + using (Measure.Scope($"Time Taken For Spawning {nbObjects} objects server side and getting report")) + { + // spawn prefabs for test + var totalAllocSampleGroup = new SampleGroup("GC Alloc", SampleUnit.Kilobyte); + var beforeAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(); + Measure.Custom(totalAllocSampleGroup, beforeAllocatedMemory / 1024); + for (int i = 0; i < nbObjects; i++) + { + var spawnedObject = s_ServerObjectPool.Get(); + SetupSpawnedObject(spawnedObject); + spawnedObject.NetworkObject.Spawn(destroyWithScene: true); + m_ServerSpawnedObjects.Add(spawnedObject); + } + + var afterAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(); + Measure.Custom(totalAllocSampleGroup, afterAllocatedMemory / 1024); + var diffAllocSampleGroup = new SampleGroup("GC Alloc diff for Spawn Server side", SampleUnit.Byte); + Measure.Custom(diffAllocSampleGroup, afterAllocatedMemory - beforeAllocatedMemory); + + } + }, additionalIsFinishedWaiter: () => + { + // wait for spawn results coming from clients + int finishedCount = 0; + if (TestCoordinator.AllClientIdsWithResults.Count != NbWorkers) + { + return false; + } + + foreach (var clientIdWithResult in TestCoordinator.AllClientIdsWithResults) + { + var latestResult = TestCoordinator.PeekLatestResult(clientIdWithResult); + if (latestResult == nbObjects) + { + finishedCount++; + } + } + + return finishedCount == NbWorkers; + }); + float serverLastResult = 0; + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + // add measurements + // todo add more client-side metrics like memory usage, time taken to execute, etc + var allocated = new SampleGroup("NbSpawnedPerFrame client side", SampleUnit.Undefined); + + foreach (var clientId in TestCoordinator.AllClientIdsWithResults) + { + var lastResult = TestCoordinator.PeekLatestResult(clientId); + Assert.That(lastResult, Is.EqualTo(nbObjects)); + } + + Assert.That(TestCoordinator.AllClientIdsWithResults.Count, Is.EqualTo(NbWorkers)); + foreach (var (clientId, result) in TestCoordinator.ConsumeCurrentResult()) + { + Measure.Custom(allocated, result); + serverLastResult = result; + } + + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, nbObjectsBytes => + { + var nbObjectsParam = BitConverter.ToInt32(nbObjectsBytes, 0); + Assert.That(GameObject.FindObjectsOfType(typeof(OneNetVar)).Length, Is.EqualTo(nbObjectsParam+1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn + }, paramToPass:BitConverter.GetBytes(nbObjects)); + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Debug.Log($"finished with test for {nbObjects} expected objects and got {serverLastResult} objects"); + }); + + } + + [UnityTearDown, MultiprocessContextBasedTest] + public IEnumerator UnityTeardown() + { + InitContextSteps(); + + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + foreach (var spawnedObject in m_ServerSpawnedObjects) + { + spawnedObject.NetworkObject.Despawn(); + s_ServerObjectPool.Release(spawnedObject); + StopSpawnedObject(spawnedObject); + } + m_ServerSpawnedObjects.Clear(); + s_ServerObjectPool.Dispose(); + }); + + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate = null; // todo move access to callbackcomponent to singleton + + void UpdateWaitForAllOneNetVarToDespawn(float deltaTime) + { + if (OneNetVar.nbInstances == 0) + { + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateWaitForAllOneNetVarToDespawn; + TestCoordinator.Instance.ClientFinishedServerRpc(); + } + } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateWaitForAllOneNetVarToDespawn; + }, waitMultipleUpdates: true, ignoreTimeoutException:true); // ignoring timeout since you don't want to hide any issues in the main tests + + yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => + { + m_ClientPrefabHandler.Dispose(); + NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabReference.Instance.referencedPrefab); + }); + } + + [OneTimeTearDown] + public override void TeardownSuite() + { + base.TeardownSuite(); + SceneManager.sceneLoaded -= OnSceneLoadedInitSetupSuite; + } + + private static void SetupSpawnedObject(OneNetVar spawnedObject) + { + spawnedObject.Init(); + } + + private static void StopSpawnedObject(OneNetVar destroyedObject) + { + OneNetVar.Stop(); + } + } +} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs.meta new file mode 100644 index 0000000000..492f738794 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91f4160a1de3f40c1bcdb9c18daf9bf2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 36af887ba2..04699ec7a3 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,15 +1,16 @@ { "dependencies": { - "com.unity.collab-proxy": "1.3.9", - "com.unity.ide.rider": "3.0.5", - "com.unity.ide.visualstudio": "2.0.7", + "com.unity.collab-proxy": "1.5.7", + "com.unity.ide.rider": "3.0.6", + "com.unity.ide.visualstudio": "2.0.8", "com.unity.ide.vscode": "1.2.3", "com.unity.multiplayer.mlapi": "file:../../com.unity.multiplayer.mlapi", "com.unity.multiplayer.transport.utp": "file:../../com.unity.multiplayer.transport.utp", "com.unity.package-validation-suite": "0.19.2-preview", - "com.unity.test-framework": "1.1.24", - "com.unity.textmeshpro": "3.0.4", - "com.unity.timeline": "1.5.2", + "com.unity.test-framework": "1.1.26", + "com.unity.test-framework.performance": "2.3.1-preview", + "com.unity.textmeshpro": "3.0.6", + "com.unity.timeline": "1.5.4", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 253421d55c..66e5bf626c 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -1,19 +1,21 @@ { "dependencies": { "com.unity.burst": { - "version": "1.4.1", - "depth": 2, + "version": "1.3.2", + "depth": 3, "source": "registry", "dependencies": { - "com.unity.mathematics": "1.2.1" + "com.unity.mathematics": "1.1.0" }, "url": "https://packages.unity.com" }, "com.unity.collab-proxy": { - "version": "1.3.9", + "version": "1.5.7", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.nuget.newtonsoft-json": "2.0.0" + }, "url": "https://packages.unity.com" }, "com.unity.collections": { @@ -34,14 +36,16 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.5", + "version": "3.0.6", "depth": 0, "source": "registry", - "dependencies": {}, + "dependencies": { + "com.unity.ext.nunit": "1.0.6" + }, "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { - "version": "2.0.7", + "version": "2.0.8", "depth": 0, "source": "registry", "dependencies": { @@ -67,7 +71,7 @@ "url": "https://packages.unity.com" }, "com.unity.mathematics": { - "version": "1.2.1", + "version": "1.1.0", "depth": 2, "source": "registry", "dependencies": {}, @@ -102,7 +106,7 @@ }, "com.unity.nuget.newtonsoft-json": { "version": "2.0.0", - "depth": 4, + "depth": 1, "source": "registry", "dependencies": {}, "url": "https://packages.unity.com" @@ -117,7 +121,7 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.24", + "version": "1.1.26", "depth": 0, "source": "registry", "dependencies": { @@ -129,7 +133,7 @@ }, "com.unity.test-framework.performance": { "version": "2.3.1-preview", - "depth": 3, + "depth": 0, "source": "registry", "dependencies": { "com.unity.test-framework": "1.1.0", @@ -138,7 +142,7 @@ "url": "https://packages.unity.com" }, "com.unity.textmeshpro": { - "version": "3.0.4", + "version": "3.0.6", "depth": 0, "source": "registry", "dependencies": { @@ -147,7 +151,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.5.2", + "version": "1.5.4", "depth": 0, "source": "registry", "dependencies": { From 5803269e30a3fb26307d851ea7d574a1b261de26 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 17:35:28 -0400 Subject: [PATCH 07/66] taking changes from wip branch --- .../Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs index 8babc0bf17..d49d2f857d 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -24,7 +24,7 @@ public abstract class BaseMultiprocessTests protected abstract int NbWorkers { get; } [OneTimeSetUp] - public virtual void SetupTestFixture() + public virtual void SetupTestSuite() { if (ShouldIgnoreTests) { From 43b3cbed602f86e68d65afbc71af1874531751d5 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 17:36:02 -0400 Subject: [PATCH 08/66] taking changes from wip branch --- .../NetworkVariablePerformanceTests.cs | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index 25292e94d6..30481a6613 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -44,9 +44,9 @@ public static void Stop() } [OneTimeSetUp] - public override void SetupTestFixture() + public override void SetupTestSuite() { - base.SetupTestFixture(); + base.SetupTestSuite(); SceneManager.sceneLoaded += OnSceneLoadedInitSetupSuite; } @@ -58,13 +58,16 @@ private void OnSceneLoadedInitSetupSuite(Scene scene, LoadSceneMode loadSceneMod } private void InitPrefab() + { + if (m_PrefabToSpawn == null) { var prefabCopy = Object.Instantiate(PrefabReference.Instance.referencedPrefab); m_PrefabToSpawn = prefabCopy.AddComponent(); } + } [UnityTest, Performance, MultiprocessContextBasedTest] - public IEnumerator TestSpawningManyObjects([Values(1, 1000, 2000, 10000)] int nbObjects) + public IEnumerator TestSpawningManyObjects([Values(1, 2, 1000, 2000, 10000)] int nbObjects) { InitContextSteps(); @@ -196,7 +199,6 @@ public IEnumerator UnityTeardown() StopSpawnedObject(spawnedObject); } m_ServerSpawnedObjects.Clear(); - s_ServerObjectPool.Dispose(); }); yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => @@ -217,7 +219,7 @@ void UpdateWaitForAllOneNetVarToDespawn(float deltaTime) yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => { m_ClientPrefabHandler.Dispose(); - NetworkManager.Singleton.PrefabHandler.RemoveHandler(PrefabReference.Instance.referencedPrefab); + NetworkManager.Singleton.PrefabHandler.RemoveHandler(m_PrefabToSpawn.NetworkObject); }); } @@ -225,6 +227,7 @@ void UpdateWaitForAllOneNetVarToDespawn(float deltaTime) public override void TeardownSuite() { base.TeardownSuite(); + s_ServerObjectPool.Dispose(); SceneManager.sceneLoaded -= OnSceneLoadedInitSetupSuite; } From c804d641f1e8c54a986386e1c0d429597f876b68 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 18:07:28 -0400 Subject: [PATCH 09/66] commenting out ExecuteStepInContext for better PR clarity --- .../Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 6602faade7..92078a3c98 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -68,7 +68,7 @@ public void Start() NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; - ExecuteStepInContext.InitializeAllSteps(); + // ExecuteStepInContext.InitializeAllSteps(); } public void Update() @@ -256,7 +256,7 @@ public void TriggerActionIDClientRpc(string actionID, byte[] args, ClientRpcPara Debug.Log($"received RPC from server, client side triggering action ID {actionID}"); try { - ExecuteStepInContext.allActions[actionID].Invoke(args); + // ExecuteStepInContext.allActions[actionID].Invoke(args); } catch (Exception e) { From 5a1d2ef5f7332af398e7a64538fbd1a99b7db9bf Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 18:08:30 -0400 Subject: [PATCH 10/66] uncommenting here, this is where they should really be --- .../Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 92078a3c98..6602faade7 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -68,7 +68,7 @@ public void Start() NetworkManager.OnClientDisconnectCallback += OnClientDisconnectCallback; - // ExecuteStepInContext.InitializeAllSteps(); + ExecuteStepInContext.InitializeAllSteps(); } public void Update() @@ -256,7 +256,7 @@ public void TriggerActionIDClientRpc(string actionID, byte[] args, ClientRpcPara Debug.Log($"received RPC from server, client side triggering action ID {actionID}"); try { - // ExecuteStepInContext.allActions[actionID].Invoke(args); + ExecuteStepInContext.allActions[actionID].Invoke(args); } catch (Exception e) { From 23e435aa5d15d8b5919edd161b76fa11ed646fca Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 19:36:49 -0400 Subject: [PATCH 11/66] cleanup --- .../MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index b7917a266b..1b7d2b24e1 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -80,12 +80,11 @@ public static void DeleteBuild() /// public static bool Build(bool isDebug = false) { - // Save standalone build path to file + // Save standalone build path to file so we can read it from standalone tests (that are not running from editor) var f = File.CreateText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.buildInfoFileName)); f.Write(buildPath); f.Close(); - // var buildPath = Application.streamingAssetsPath; // deleting so we don't end up testing on outdated builds if there's a build failure DeleteBuild(); From c38a957ecba73423c0a8088105164424f7375dd6 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 19:41:09 -0400 Subject: [PATCH 12/66] better name --- .../MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 1b7d2b24e1..3e9d4d82d5 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -20,7 +20,7 @@ public class BuildMultiprocessTestPlayer : MonoBehaviour #if UNITY_EDITOR [MenuItem(multiprocessBaseMenuName+"/Build Test Player #t")] - public static void BuildNoExecute() + public static void BuildNoDebug() { var success = Build(); if (!success) From 60b7d43ac7a4aa2dfbd10b3b584af7483cdc8c44 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Sun, 27 Jun 2021 19:55:22 -0400 Subject: [PATCH 13/66] removing comment and putting better name --- .../MultiprocessRuntime/ExecuteStepInContext.cs | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs index a13417ab40..6df0b129ce 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -50,7 +50,7 @@ public IEnumerator AfterTest(ITest test) private NetworkManager m_NetworkManager; private bool m_IsRegistering; - private List> m_RemoteIsFinishedChecks = new List>(); + private List> m_ClientIsFinishedChecks = new List>(); private Func m_AdditionalIsFinishedWaiter; private bool m_WaitMultipleUpdates; @@ -169,11 +169,10 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s m_StepToExecute = stepToExecute; m_WaitMultipleUpdates = waitMultipleUpdates; m_IgnoreTimeoutException = ignoreTimeoutException; + if (additionalIsFinishedWaiter != null) { m_AdditionalIsFinishedWaiter = additionalIsFinishedWaiter; - - // m_IsFinishedChecks.Add(additionalIsFinishedWaiter); } if (networkManager == null) @@ -216,7 +215,7 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s }); foreach (var clientId in TestCoordinator.AllClientIdExceptMine) { - m_RemoteIsFinishedChecks.Add(TestCoordinator.ConsumeClientIsFinished(clientId)); + m_ClientIsFinishedChecks.Add(TestCoordinator.ConsumeClientIsFinished(clientId)); } } else @@ -267,16 +266,16 @@ public override bool keepWaiting } } - if (m_IsRegistering || ShouldExecuteLocally || m_RemoteIsFinishedChecks == null) + if (m_IsRegistering || ShouldExecuteLocally || m_ClientIsFinishedChecks == null) { return false; } - for (int i = m_RemoteIsFinishedChecks.Count - 1; i >= 0; i--) + for (int i = m_ClientIsFinishedChecks.Count - 1; i >= 0; i--) { - if (m_RemoteIsFinishedChecks[i].Invoke()) + if (m_ClientIsFinishedChecks[i].Invoke()) { - m_RemoteIsFinishedChecks.RemoveAt(i); + m_ClientIsFinishedChecks.RemoveAt(i); } else { From 98bdf23a0b6f34be6ec01a7c2141970fda21298e Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Tue, 29 Jun 2021 19:01:56 -0400 Subject: [PATCH 14/66] first iteration --- .../Runtime/MultiprocessRuntime/doc.meta | 8 ++ .../readme-ressources.meta | 8 ++ .../readme-ressources/Building-Player.png | Bin 0 -> 19604 bytes .../Building-Player.png.meta | 96 ++++++++++++++++++ .../readme-ressources/Multiprocess.png | Bin 0 -> 52653 bytes .../readme-ressources/Multiprocess.png.meta | 96 ++++++++++++++++++ .../Runtime/MultiprocessRuntime/readme.md | 32 ++++++ .../MultiprocessRuntime/readme.md.meta | 7 ++ 8 files changed, 247 insertions(+) create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/doc.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md.meta diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/doc.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/doc.meta new file mode 100644 index 0000000000..9f17fffaab --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/doc.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f8deeb8a7251246dd99384a899d4597c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources.meta new file mode 100644 index 0000000000..00a2ce978f --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 5d13190bb106b4188934d5576df7e777 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png new file mode 100644 index 0000000000000000000000000000000000000000..3cce0f04d29359936253432335164b6e7675938a GIT binary patch literal 19604 zcmZVl1y~%xvn~$f?i$>K1PktqyGw9~;O@FukU(&E3-0b7AP^iDcXxN#{qsBL+G&!p9L~FGBpILdr%$ zLRC&ef>PDh>AQ`+B@`4xg1LzajvO_uvMif^s^~lH=braxN-!SE1-{1VH z{G@aPgRM_kTdaWVU#U-ITmGv zSzr(!cYA(WSa9%H_|b2yY>ZI#fx6R2Qe8U9gK05+i_=!!leiZerKU_+nzDR)1KGn;b24z_rSIss)O z%i+JWBXzK|pRq8=GRubv23?aVxMbF2hfQ?n1P6PpKp=U8M4y(pi9UgiAP~so!^eU3 z1E-!pea>4XP_@;js0%sec^|lbv(%CMuA~IT^dTcbK}Xm?!F@>3ABFg%KtaJKhC?BK zoN+#iWIoLQ(Ly8V!~P!`%H%@_C8i-EC--sIFn6`Ibabmk6%+WCl7aF zYU=+4`oEw5{+*WIHvcb@quc*<>!X8g|Ix60Wo2jkzrH_Ah5l13plai7X|F43KCj}bWOr#?b z6NSeUQ^Ol1Qk9g#llqhW$32`gC(q0GzZ@O_6`UkI_CNkFDAWl5kl|<*RxTla z3*2*pf{jFj{LO;v3tDX23#Zx(!rXLpzw}ah99HJ_TC7bcGPzsT-&4|$KdDD?{Js~D zI#fuZ$30D9)5?%gMLRQ_$zM~TDV}8df^FZ2!BtL2h~u$v=rmfa$uvN>W1P8@w_hl* z7hAVOOo!aXPjGNqmtXY7J(;h>##gKm&`~J&jW?+AZ5yBAC|OPyoI$NB*~kpE#eJcR zPEl&3R1F7U@e3U6=cUrz#HkM&Kqe_tcaC2UZ)y*3p!B~2`>1~Hs?m^`#Was77fDSx^!Duu| zcLz~q+|xL}6cB*)u{uG_DcjgBnKRCTlL8UIo(9Rt#P8Ls$t@0@W^HiOi0K?J5ox;> ze7UAZ>f)AIGzCf7s&%;)1gB@~E7znvHbScXXk>hQ`4@lUCk6o%gI_h?$k+yjuT=CH zqYuJCzlqsyFo_ppfvIBuX81CUh*3=wH5A3-h3uk=WnVhuYiOJ;HNF9_+)mqBhm5b} z4wW>Rr;MAgJa5|XlEn5r0-<#BxD2{t4~tiFv|(~X5ILKAT=Uyv#`t{8CT8Q_w2ql2 z-n}*htlSR%5pev)PHH4X`sTdOd*$WG&% zS*!q{VHr{y&hwc5SrlYN0Pd9q`AeUyEaVioT42)cMj03EmJ78;)pi;Xp(v^WgslFZ6;=u0 zI}$>iLX~n@EAVdh?3VH45Jis#4_+|;RD}msqSqA<$`BHki<)qt0tuy{=(o6wdxM?u zf0N>;J;dyVLM`Z%mf?_dafG0b7C;CO^6DZb2P~J1ZYKsQ4v4@=2}nEiE5=cih-e%zY$Jo=vd)Vlq_HwJx%x!>ENgeiv6dJ@sq8dLcbuJ6Zs0Xd&j=P zBNT#BZ?!G?!ODh+PoWgnaA5YGq;X?*+O$yr;g_Iq1Djtx;sfAUt8_Wb1dTf08{dlF zVF6Y|Xla9+c9IdE8o3HLldR>TDi!ar86DP;hWA=#;I(40vzq$by8zP3ON8B43T^gd zZ{iR(rQf`uDD7fGV4?XDL-6mL`@%$L2Ft)x=Z;Q63N$<{8#pJZ$Y}cS*TjIZW2sMA zP-UuwBI(@{RHRY@qO@a@uzVCLzn}cvw;7=0%~^r+eJBxqn?9-~F}DNhJ8@M;X%WCO zrf~}Rgz@V{nzsV6*`{Qgh-st~Zb$kBL@bUM+BY&#w7cx_rSkY+QLGo;SHv4lj)0<6 zUrTq{TTB>9aFi0%zbi!gEp@!7h*Nc922Z5EkXZ}TAJm7X=DJ|k$i?r^u$ghdc=9&; zR~fK?)ssf5KgXNLubxT3^y6vrJG4}X34{xX&-U&2W3S2L+gpDGQde+j)+gs1*tbFB zvhiks^9(pRk%IFfYgi)G5eK8^f(b1g6?q`?B7A&LCO z*QwT@rbex1NM~V5(>DBt7TqUvcpsV~V{LlO#O^XfKqL0wCp@mNk?56QQa;ic9x}a` z&|5QKPBSvPYHD8oB$$T>=~Gl?1Y_hz0Obk+T)}d;UOhf{m;&(SYk^mP2PqPvd0gNu zpXQLqPbf4|-1!l}I!w0YXA~VyRA45RiiS5jc=W9|sln5N!Wc<6vTWqpa5z~gRN`c$ zX{7?!6;52nR6C$4-8sb*qD|vlIi?T{&`>mVuQVQ0SgH#*LENcm(y!v@-^$tN=!jKg zDaS%6!Y4!@SYSzeg@Q@rgNn6_YX@nP$kAJpKjj5=%03&pLe)EVAAI!K@25%zv`k6* z{t|a*(cmy?j5^(S8$zNlJFwXUTIj>-x?Rz7VqkXqj-B&1lwatyKa$x}lZ^K6`7oXM zW{|%01|eHxx1IRx1-p<3hiiC|!r_ZWq;+3FG+ z+(hma77{>#f&DZ;y}W-jtZ2PLeav8HgfLSD?udVfQR;pD?3yAEB&6KNocOH3-4c#Y zEYQQ6gaHe$7>Pc1N^t_Gn9_*+hAqgDzF%wbg!vN@Z%dXH7(Z8=Fn*65Hhzovwa>4U zKb_O#jteMvSm+I$Ho4>EfMONNb$Jt=LjIU!goap(dMP;?)L4nHaX-gu<+f`l-bRwy(>b4chGpVu;5WDmU4D@E}a0; z0S%QRiW!{1PeJ1ORs}YJ&2PpIT7KvEt)x8aTV}TtJoUJ-fR(@}(}wz&t|=qGrC|~W zd#CREmLn-Ws?emU_w%7%ayy9E+gDFWd;}STyM1FQ=Gv5F&oQxGtC{|3pojPG-`rJC4_&*dFKorK@3?YYuzx>cUm8jjwL>KC+4v~Iu|b$s~w>rGZVbq z1YH1~%iNHZC{-V4uYcC$TzL_BAXR4x3j+I1sRL#m&Zm``%I{z>sMJmbLI{rsCJz}c zvWxQ!f2!^+79dE{h5_s5l~*5I10FZl9eRJ9ZCXW72>1S;w5wTOFaqwfY$K9bJWB)@ z7xA)dCjhN)eeX}oN6KqmG_EjEV?-FidyOyjPeRpKc-G2UA}m{$u{*^T-H&VQv6`K0B4zVsBq6t>VYrA9N#gJ?xdGW>zB$d+@Ri<6GQvg^Va6yX^vz^?a4dT*){9mymVs)4QIYUsn=0EbIyT%p4 zqy0AEN#6Lx-R_M@Ppeyl}y3o&9WFOmLo!&-%~sHi6Qlewc@8G z_E56t&NfSHLE*&&(Co-x7&ej<*ZT$SMu0djjM zDl%op0flcL3$KyG<61!U-aM_e03T<54q!33m&*;M{_yGdV6_$(}#hffCAns`^;lY$b1kjEURjXl*QDwMBw@`hdtsYE00!%>vP;lyy0&H9| zNNlbD8lu2>73ega*DgYbwF7vN&#v)TXp=HpN62ci1D$6LmDP|~VjWn|<}~BLg%WEh z2VN7x)7sNP48Z53icYK3Z`HqL0t(>lonaw=Tq~-p1K(e;2!&j~P}3!D)ff~aVK~-A zJJS_;K}q(3m09mZ)BYZ7M4Ce3=oA9TQ1pt`RD_NlT6AlF4}?wls9mM{0l# z&l})5C~dKa;jk;aRVe~j9hF0-WvWa{ij5EVkDNF@80S|$F}M3>kG>wwkM)0$W4+GH zo<ms3%77`f<5eTND zC;~q23%51IrR~i|B`MyNG`sVc+Gb%ig~fj@%)!N$K1&u>P+77$g z?o?s^6SdVV6%xMB;ax9dz-4Z2V+0ra5DA+(Pk6s8a3?pVJeSt;TsUU8N=M2HOK5HL z?W8dJ;8ITOx=E-G0F21774dm8JUnZBqsayvbS`O z=z1yip|9I(1*CtUN>toCG@KDj#J@)FwZfqR>}6!GaO zg4Yr*P5AQMj@!j@X{U1QhJQ8nL=DI#CFQ()hQ|NrPxBxnU9j2C+wra%2HYMEe;JLB zHLFk8u0*M(>8&7-!;8C==xODAp`uQE1c zSFzlV^GnKay}Rd)D#MjIj`f%uz(J7=c93HSSpF+&i%y5fmrlq=r~TV&?Wzn#k2#^S zd(G*YxuojE9nY-;@1_avaJS{|NRMfeG)Chuo|Wm$3MBw3q$J1Zdj>JJ#>V8=uw~MqS~3*vd5TFnU&dG@$~lhX8q7$$xDLgz9kbI) zarwIIZgT_s9c2xKQgqZUWs9E&p>(?o`TDIR2phJ;r?I3Z01|j1naS9zmA66MXZ{0G z_o)93Uz}}}tF?!#3>H=7*m+DYvJaT?JMH1h zx#b{*VVKg(P%>K{?#d2A$1+N_kA)}=nd^QjPxo~J*+cMuxkda=(*EY2KQaj&&(Q`_m z-*G?7BjxKv<^8tyu=;RoUA)Po--#B`da}EC*$q%$v^>Z3Z$fJ?zWsg|x2QY{T}Q$g z1FINWWGpdv)%~zE@u0~yk%76(+T;*ba;sfo>|ljXc?8Quq$4s4Bb8Dq4fB$YXUf|% zX=)O2Td9{_;C9w<8_VN<bqrb{ix-UJOw0Cu+X}(q*6f{& zUA2QsU7?8+E)ioN1bgmWgMgh5{?|wuI;bWyH%By>UN0mzhJ}qF*4)4Y{;X1)h%9XS z;OrL@tTu1ueYzb;pM-=1S+&QRfOF7#;33f9;BQjt5ynjeNWfQKgosTS@ydA>Q9O!- zOHo2nP1;1w7F3nNbG9whq~lxGE%+czB4F=bsAvHChC*y*-#{M2tXdl%WXh zYgy__^O7ZO$!CQ5e!J_IkNn07!u0*mNGOCWgCR3(ocf*+{3q_dg-m0F>{d*?JlVWBK&v zLca05mmU*$O{__~XA&Cw{vi)IbLsUoH<7)}Z=i@HJUvzSqKN}Zeo;`!DRe^rRh`8 zkJU(#jcPl>RtoIt=##A2NTtMweEp7a(nOJ;J0c~8{05oU5hesLHG`(i3Hh)p+1kH#*ZI!@u)T+^wF}On$6ojcmQv0~@EddA@~m#QhGvqT>qN zy68=zM!P_G+<45-@jUQhI+!dGsbYk!>CZ{XQp_HjI;Fepw9StLfHld!3!bjI-~&&t zM~i5L1g?;%zQ!vDhex_ThSUIE6oncJS_CS#*d4ol?~JUH`S7@g+L@c!dfsNq;1=gf zlv^pd)0`F^rIzE$ED|>5;Lpl6Mq(!)-(7IuV#skt-bNi}>UYOl)FU1q_S1}y{ZQO2 zhNO68u;_f93IH0mbmUpEj%N`HT^?gR+or);`XifAXlBWbv z)nZHEx^3#J8S2zE_AHTKU1Fh#&(^y`EUc?zVU>4{=_js{8OlfBG^c6s>xpz@p)h#J`c*auGAEZ* z^?1;2FQFu56maj+&j-6LLtLf&1DbnVqL8%l+O_Qtv7{nL(1R=2@p+xxQ@*zMxg;+k z7~Xqp_!oq7l(2pjP(hD89wu^ukLiO3EAREf7G$I`BvHd~UD%&<^KBKnn zM4&bTfuc%zbPCFCN+m&dsMBq;y7jL8eR0#j5`lDg{K6rra#*>_1}iyr0%u zZ@%ee+P4i$aWJ0rS;>_@J;eX^ymc&V)vW;GQ|x2o6yT8?^`dJnC5w4s$aCE$D7%m3 zA>?Ai&hyk(@T{rxb^w8FrFC(BN2Zd^r>6J%d*@N&xKNkK-4-l<`~9N(0psP}8obS% zQ8!P$c^v;N*!OLQ$6%7vo%wIc6}e!Wqiohv5_H-3lr>|%DO_ctKSqK3TH6ba_}_Gt zH3XK$y@klh+Fmyrqy$Oo23s!TF&4NE|G9Df!l{0_w`4oU6b5-)2>m-e|6xN7+}D(b zKSxPU*QkwS|F~FKTVd;1jZf4T=<}2t484|@PsxFA$TJM}Uo$7kJVz-h&rAASkCE!* ztrtc4DxJzk+UIV5KO9@-Jx|zv%mRyATfLr-7s^mXB3XC8ALD9#Zw}hj9nPCU?nENs zJq^F({YCqeCL6s+zDcqN35oER4m1_C43uAiH^(NSCOD-e7Wq(q$C0CvMMnl8jCX*E zTG?q?(xfsXKRn}e7le@sgK=JCQs@2cmd(FQ99{3Fd?f1>pJ@dc0KHh!Eg(zW3yu^FQDO1F{1Y{})L8I|Xz5E+$F z43V`Gl`K{t`Lh;5a5a3L%%0Zk$~vTd@!0zi4@mXW=PpsNvoAkKB|c!lic3C@ zEOK)sD|iz*7$bPgIVDmn_!?J3GKry!4EcLm|H?XS_POBbW}L+?>AnY%2P&Kq`5}YT zuSMXmKTEiS<7FJh(&&TK#kz`d%ua$Dj-;=DHg2UnEha<$uwMyG4u->Z% z;852y@Q39*QK2@G)7Y9Gat`O!$JTXK=w%>)WJi0L>q zuGiic6EKXE*CCa!Myc}LT3aPs6~@AyL*`0eOXXosZX`T-V-^`QomN+!)?p$d)P{x( zrQJ-{MWb7TeuMXc3Vq&ajmA(mefapHVVg|kAhgyR3r#rN+0qb~5BA&Ijmc%sPD^K1g*3XyXzPFwp z&LCal{hIYY>VE#d@x3V5Ti$L($Kp(C^XSQ9@pezaXvl{Oax)8Faa!{MJ5tI8i zB25Jcv^tTsq-NuN^@_GJaFEOn7HJwk5t#F%DmDlTjkN>O3WpvpVO_|-xPN^YB{(f2 z-RjrO0N&=MlTL2|i$R^Jvu_iFZ=VaI&`qSo`casvqOM3b`wZx`J+^mtC z>W>IVu8<`2>N(|9feoy=m~OShzJc<%DT;e*oYDejew{mr=Ak8IPG{>FRb@h~7q!EV zJF=M+HLl<6QUUxGqszZHC}Gqnc{zFSAdZ}FQ-J+bMAz?qm?SKrpR(k&2{D22-7CJn zZh|Y0`=D7*1uuI_gFRC_#LCf0z-85KW;zglWiS7|Ze!H%T}x7a)n)iE!Gf4q&H3=C z1WdNXH4IMa+@KEWS|y%`NMsph#6D!s6w`zx_(}6P5mLFcQ>PxaAs7DYD&VpTaO`o12Acx-t*OPM1i-F z|Cwx-dPwkzp7K_~(hdc@hxq{JTBj1z&bFj`4eL~2%2ilWYZRf)(%eFpB*jJF%Zg%K z_3!cQrh{L7{CWz@*Gn4i&n)7iY2pMX9oxW6t9hk*97`YBVGenG)ZlWUt+J}WvoR)h zZkyfSfGR-c;sNs?B=R`tqdrCj(g8_Qq zjE?_sMBU0ZiIS1-XG~q$*6SqppY!3WV=cZ2uPSnLZ7ZXAjrdhAA@vpur;2}Qa^Ga5bC2>3 z#+t6kDMId}8O`}e4jNlj!C@5kajDd| z__Z}jx%q%4vz&ACHdfN&00HzjIENqZ2NAg0WO+ysKS7?EQ1bIOUnwI_P+Atb(6)pt zQ8y~PgzN1C0~{j_xBb#+>Z~D{p5L79-xaT2`C|M#Mqwvuilc%P7Bro!)OI$#`=9hl zl5Ad(PL+(G!ZYzi)pBG0&W#J4m2S1fRrs!dH9h>S7{?YdkK@?`YMtJp3smtpSFjo? z13Uwp3;26pfI!raru<+6J+jI&+NX?SCLU;2=p+NU(bOS5Ho2b#%i^e$F%;DO_TtKi zwp|eeEBJFmu5atHA#R=nGs~t7vfh&XjquKDmSKN6CX%8h6DzFY4%3qPCHCP@rm>~0 zgx6&kc2bgI90`85Zg*+8JxI32mZ0B8%noOQA<_%&DtPY-fiS6@F%iJ{D>bV+W zuPZ^dh94;YCCpk3^0byA#UD;noFgPT^J|zg>cIX-2@z;^vlEkDEgK5C?(kqD>kCfE zEuY#yZ1Q)B=7Wa6t3i0CvPVw^^QG8W$lsN*+`CO?{Po!-R6;_op{^fb0Pc=ewfOSZ zGX$)q>BA4k#VEOo1*u8s=iy4?YEcvKx}B18jIBv?!3xt}Ots21wjArlpG`|fXt-t|e z?g9$F#GCJpI*Oj;$zo!4ah%2yIbZweZ3jPA*_yYyr*M5X71 z^=lcyMKEKeSl(2W?2FjEhb#T-xrrL@DqjJHYL8IZp}8a+793%3vx2{V$|46?zg`gr zHEIR3VS8V~vEJG8GO#O4SZ-YR{~DIF79i(y$3QDj=0jRU%rK-HD-Zkp3ps}pP#o;i zzII%WuubrK({;ng(Qi@(hbgbjHb?8Cq+ z%XFlQL#g|VRs_TtwHIPAXuG9~vN- zO`^vkD1ui|(70Q<_DmBswgubM3TR3UlaRZ61S66@+>#*5Q!{(-6<|#U7OuU14lj96 z5&XG$t8;CywPD9fdoH~;pku0gxvlMok7crXy*@JN5=?nddz!f*i^L`?-DF+mH zE+vz*axmjhw-2gh(ND3oddSw{rK=(0CtFe??V$Ezw?%TMv2=+P>N5m0_5!jtj`#*W zainndS|}881`JO#w|!Xoi)97e14m})Al&VIgx}71oP1%3#tvR$Mc?Nm%+683rzM3hG{YjJ{u*QqcbsvV z@&bzd?TiS3nf9phJ=QfXOSePFTA<=8K&L;Bh`+90A^QCz>9~Ew_xwW>9W$%I!6X<_ zcxRWXI6qA62pQ?Lts&F7!RDXh9qH5fuh>?>iLzXEG10-I)0p(arq5j4X?RdF-Ld^W z*C@!G0~Yzo*LVf}7^hIvNsf*|XsTGW{j&0$*$s=(%+46U>#J!LlR1nI57|&6vHwhB zX`0$!>7)fKaK$ZRl%ih3CO{pgYZK_yxY%ctX`9Eu6TlTsldyRjn6yjL{+eREO_msq zF&2KhMbM9V^qj*@q6OxUHHOu|dR!#p7z_yTUKiB2FS;m&^lG^NfICbO&Bwa<26aq9 z#;3qQs0P>7I9-|3gTg>lr6pCOfx;qN>V^SK>$gFPEcQQ&VIbcd>SHaa5svCT#Y(wT zB^$mXjDLzy4n`Y=S#JXORbV~s;aTsHJFjU|mKUGV=srPvQ{e4chseK%cS4 zG!kV2j3}0eCwM-o>s@F!4O&f?LJ^xG*sV}5gE|YL(Q0DaZb6~FCmn0}-$nvm$#j^3 z9Pwov%?7gRtGrSjBgYBw;4FhRtN03_-S1>9Gwj!&P#6K)t&HSRB~!^hO3SB_pFx`GK%xPRGddA`xue7FJXNJ@l^2T9 zx=ExG`yLLqV$RAbs7t=tR-+<<-wMYL(i_!IAkEsN4z#H4M6pA2DpPTZ_#-|G8-iN- z*!i!Yajv_#`ocb=`Fr50ZLKvmd@Bh}TFm3k_JcDVq-U3Vn$Zz236`SMg#SBwcvGrD zF9GNakug8qo)Y!YN&nWjiTqrRZA6X*Fu?GxV_A@*IVVtEYM z;P3`fQjhjQY|737%~`)Lw`ArpMWwW70|84Cw8Mu zN(9@$?PceQgycLRh&&i)L^gu+ZGmlQ{?wx^9!PCRN%gk(!AFf{ez#}UH_in*qlhxJ zV+oX=QGD&{@n+D=w+=3ZcV(G2+3E{^Xx>-^i?IzcS00`^thU@gPWU`ChOf2D{y8m# z53D69lgq=HW08XEup9BeAo5d{W`Y~>(xi@oCE%Nv9Of#|+>y!2|AVTvdGtr12SaVP z!BSz-{ueiBT0md8n~`S~z!z1)kW5gM&Zf83>KL4fL3vk!?&xs^WE6Q`L80hdo%SzC zq(jFn*Rt{ zR6#KC0(!dnI}1*;FkotFADMdD-CUJjWZo1PJg}#Vqj!UMMsr|N2mTKxy)9m=;JtdQ zobeQrNA1yky}n>|cvm(9qh7Vb7g;UiRklkt0=8b$AJq6?AX@~Zb5`Ij0>;unQj>kA z8v;#C0k)`2JOXDtN!(m#ycgO__beO;KIzaOD!K0*CCridl^I2)AIM=GskxBf>0xaO5vuFV0dB(i zy(S&|MAa|5_E^FNWr#5Eko31B%c$k&+zLj)^x1TvlYm;ewGXso^9{|$xty$7vUr+` zJPjrg3|}&p-ZE=136v=UIOjIr-8&}IJ5>SLI#a}_bH!>6hhi9dRaRREObs4WEubpo z&Z6{YV^7Ac-xH3&%n75U?|Ffx?+BIo(Xgc$)%)(b?Zt71oNwlx9`mK;nepd^;ner% z%fmg{9E+(=3rh$Nb2GUKNqjC>f+;7sWq5HAPJ%L%poQPD!rBfF+7#_I+{P8bhL35v zo;wrf=ljTjgF1%hk;!9Ahg4Dei_*JYF$s||f?_xLyU|P9*~pYVCnD~@HDus~ zK;HZzjI-65*vn&7cA`10Y7?iI%rBkdV{Chte58>@qC}#B+9~w>B z0vysr?o64-vcD8IyWdLlu+WSDrTiAZy!)1hZWZCyd2w`KHMKIn>RZX;V66PTckm|a zctQNUBf714$@+$^@{UbA8X5aJKIXjX=^i$m(D^9|GjR- z7%jqzoS!?VcqK*P%d!agb2qFD(X>39=VnzX*;;Lq3=u`tg(M9h9ljk%~ zj(vOTuQPq6l$wM_zknT0zpEytoQu3yua+EM8!N$_ckv(o2NT8?n18pc+1j;j=Bl!j zj!KUI{`K>0^blx$I7admI0WpT4H70j6Ez=^u??O-VgBx zcBSkR(=oDprZOLzg&1exZwD11U9r_2W^VlXsSmB|9V@SS5R20F=N_e0PJze1W+Cd1 z=WF&q-sgkmp1bCGno%BTmn9R-Kp6lezq!h7^}FYv4<@AVitjo;8sc4SiT)SXxgnm* zp03+Ierl4rx}O&GuKk`QUizR5zYB#<@Jw7KtvUA5qg8NlR!d_vrmn)~zbY%t-nUno^^-NW$J3xG*Kz3`eTzTy`9|2IOtsa>3@ksZ@866 zYI<9D6>R|#PXLW@jIw0O~%%5sVfO2vkDsH0=jNLL1VPJ6Ngt}Hz#_EyW?&PVMw z<}>ACUmaJ3SaJhTF~a%v4Sqj+NDcPeoMvUW;EZg((&=Y}sRSo~D_C$-A)tWDcMBQ$lpX&sc@CW0gxSlv zJ|%>RPFqMR#6$}DgVu}tc(gV~ zgPQy?bCp+%pN`EE0>Y$vShjDw#07SVZ?&3SEX~~6-HR?EAGU7*%ke6 zXNpAhVpIKNVXJLABTmNBQj&-kq~~ipVLDF!#al$=-3QW`=Wp7w>Fcl-+?A|I$CUUk zyp1>KG!3I*GL`^JEVoVboZyt|a#iitQ4+{+Gep_6XbCW4k2T zsU96KW1SEy0~Gpw)MUn)HHA5OlF7-&%rb3Ae$BY6>^J6cd3-FbE%+@?;}G06y7A)O zf$&KMfe9naJApBWJn-3Wx2E&(;LEcB+>_#0hQoDK5Vq%9TMqiXrb)|2tw}+$OlC1e z==}%!$?6R2jCrY&vNwkk&l^LX`~kjqat7$IlF{v~=4;++kA6*6nhEGeMn~gL!|(RM z%F>eirPlXkkbk9_dN~7_$kZ4Ci;vlhjH>D6of=V6-FafO`CjU1Af2}+VqC&;Ddn@` z)35%6PVlTw;@I!9JB^7WYx6XdW3jn=iysi@;Eb{$$C7G$q-;h7I8z%R+BsD0Ud~X6 zCTz+)v4OTjZZ8jhc->8qy(ghgOAM^ij&7g06Op*^qqyol+G?(z68N$dYt@}EQ@_7R zg!0!YSE0W6fuPL$r!M0=iqS?T`M!Mqm+~6TN%;+UhnF!IA{_2mS`NI5_`hVIv_xIv zGkg#nwOrh`QM#Fbz@~rY?ZqAQU})#6beotw;9#*;>5^TxRs~$hC?x)I-EeBzA?zh`fU1E@SeYOi2jK#g#StK z#La?*=n755RL&ZdqLEFfZv1Lcq5+mQ`^ht8;M~me@XMK3c98H^3Kfq<6(4oph6`E( z!R5ixSUF6-V!Z#5kus!DY(9^tM$y5D9$KW|06emLPJN<5&#+^ZCD?IdpJf5%euuBdh(!he}bne;eq(`TIvJQ|D>@wCwdm zWoAU&4*#pXnHHI2D>!WzomZ(jBe$nl?ET)^+K1zT)f?~e-}T=bk7#E>3iy#Qj_{aQ#aM>IDKxxNSA1OnNIeJkBXudp_l~Hqz&M7v@iTOf+H@b?j(p z2*6VW6X5Wr^4c#=gLG*8UvUo<*t(mV?U!d``F4;5@1`YgF&-kviLBT?&vdf*-AcqN zU3Q1Ta)U$uMbf{zFsu@@T2^eoP~<1KI&E%zpqS#Ko)`FH!|I(DSE=*+*gx4rvOoQ^=)AiB)c-*6U5( z;68uDKmK@tMD6v_g1c2%a4D)lCEG=>?P#(+qb&?}>^$e`{J>%@|AU^`OXl|PE2vVt zRz7EFxkf5ko+w)R^P9d^Z)3)`YX?^R=Gmflk`YkfhENy zCc$x~r#^-g8*@h4Q8G20Do#S$hlRR>&*cQP{gp#G0mI&Z+Iy!C1m&2dj_PqVD{1Cfu)=0aUiOKzEGU_s2~q88VOL zL;ka_+sVL)@2Ve2<>r=(xoJ%03ZGweUOv2xkdthOvJsY4EPjx7qtQut{@JX%O##<~ zr3?_BM-ym-i-+3nTMdEwj_vzr{fHFqSk#3P^qzFiy@L(&`8JB(dYTkI(8g?wp(p+sJd~ZYNuAs6A2)t zAcDMK0y}q$|C1i9PI2VI)MbY5Gg3qW`pveMke9_?IL_g7_lISxPPD6Wr5S_|5^}rB zd;C2G?J^xlZ%gKJKElopE6IyQBd8a` zk82tw6jH{cSFCCLwt^AuueP$rqS9NOq6t_6>L_7( z0wC{?4j)@I1f@zg7UyhcG86$#0Utn-C;aDim4N$EJaoBi4bspHmIj+QHl2~@vZY?F z4v2<)@3VSj)ajCpPSJ{4G)wW^yW`^o@8x+wWVy8fwlJ1(@VMTRL+iTZN1qeVKd#pe zOsDpJ0xxw*&sC@1?%EWCfcu4T*b!)o9UUB62h6s@>pC{SQ_eo_t_*?@{ zb^Qn@pIH2%0Fbv^BSowHf{H9_e;hLN{t$Q`pBoEfW=jDs(}6HFegege&tD(G;MYsx zj=ta+ve%jmbsA3Rb|M6UxnIp7y>Y~|4?32$Q`fb0h8PqE8P+Ph^YdBv<3{YDA+gB& zi-VmC;Jqm?AxUtB$SQmL?B?%e5GB4I=k=HnBI&^gEz?nP9Q?Y8r9}l4_$r&RLk4`i z7NMgW5YJ6#YOpcbqrox3O4u41IP^We=Q`71_==u?-mcH7Dp$q0Z894LovFMOROa?C zeWvC4AnVl-T+?}R10FT|g5mgnzvv|sR&CxCT{IB973BMfApp6r1x_5r&_p5g;hb{= z!Ty&rga*#R1Y>^7#d1m1OKhIsRS#DRSkq+?lNCBa%G5P-SG2*MoL;sAD@z#_UxZZT zWsk)adp;|N2qH5|BLo(}3Lz08pZmDNtgW@Xq1ISQcCB%$X)e~R&$|RZq(DAKah=d? z&P(1I1MudYXZxEF@=eDS{_rm@tN>NP14!S=l201JMyf%EzJD(HgcgCB(evz%i+*<7 z@%0a(u?2`(2TA$%Be!IC2QeEloI=BPhNDmmPM7qBb7Z_P*&_?lT=Lh9xS0sqIpKxdDF8J7p#|c@sxhO#orj z??|Ng|I6_{Z(e+`4uR(o@(KHOD^&k4S<2u0{|gff?DUb3k1V@%>0(R)NaRUrSTD$P zNW(A>@7{ea4bYu@p^7@@*9<}iEQ9)*Yp$_1*Id&MKKKyZYp=b0?jG;13?guHQ!Hby zA7LX*nm>6$(%p93%_9XCH?hy~;g4vbe&)2fQ#0pgK26xoHFy=t&J=}|<`wzJGj*f@ z6H3!%*}Ag&VSm;Pn9{Johr?^vuAS=%enoN-iIJGbL|(D&zW(}aUofSf^@LbYpO`%P z2XA)>wQq-BKB3EZ%E7F3L^>e+KWzmxK)bI_mz(=z1Ezy0>xepg^&jrleMhdXvn z67yoh=dc(y+TUm3u1%za10$qoP;&QPym*nbz=5zTPKSjrcfuE6e6c3%9f$Cg#99ls zL7wH8UoP|XXWY1P_Q@xo_~5{Syzux>JL5g}*wgJvpNO_#!I;RC_f0xTHKOrLI0^)-v6G-tNrcHKpM0|C@zSg+GY2KH`p%q^ z73T#D7W!c5*s-H`7D7Qf4Rpw(Fps4eC=6cakIdy6tT+w^7#*jgl>1nV2IQ{Got(uq zWVrX<`+VVwMN}64m=-bRz%g8Z{q>p1fT;+NbeVfHXwqb%1>^t#2)ju{K~yR7DfAdI ztq*rl=FEHTwYQyp_St^dnlj}_-Ld9nNb)4%*kh0N#kOeIie!&dvM5J}P%4wQFK{s& zPl4pkvL-+qVUvwF-q;uO;@ua7isWMLh<1GEop(GsA{t@Q_<{@0&qRf+A@J1b?|=Wh zm+_)k)Pu+lhxw;JgB8Fq2zp$3W;vc6m!urOh?A%T8~jg_U=R{oGhpt!@4j*?`TvtjSD+LQ;q=qb(36R5fXjLSk00y8 zQCGma##Kvla8_5SB_&JINmREb>JXmjg72Mhw~q7qIwb$QB*pZQg=7*_we(Z2-=acG zYGBq>*CioO(Mi;_HHvse3&;XXVo=tFVe3+^TEB5w7YY?=6fID+K;2p(z1CA(%0(wp zTMHKPix&8eS^!5;7tW&SBz~g}TokKlfx5ClZJkBYNz_$K6zLQ#@Ef;4O`S#2N&Lne zwkTfF0(EVH>N<;}lc?(!DAN1GSfDPSAp9CetLl11(MkLotyARr`?i3-6Zd;}5U~NO zvY~I$NknV?2G6{SSxmnn3nb1#-<$po2~|r`vTwx~B$R#swIE3T3ckg6+;PY0+kEvM zVBx|A{*6h#+>`!7P=4%C%6An`IN=0;KR@4hC(^j^BgHR3rRyro^98e(EnDRUd-c^< zYWyuvzITB<`DEf7j(o9;E$e>xVX|Nr>B~!>`sOHA`LY#XfZ{9Ud}9*ZJpAy(zF`hu z2Fp!5?zoe_y!3b5amO9YeK#Z*ws>8A3;eG{4TUsk+EI~?HFgrbQp|n|ba=iN%vYh~ zmuL8*4tpC`mVoEmm{m#aR>U@J2OoS;Rlv)@@al8CE3=nDdPnU^lRhm2Th$A`w8lMH`wgCZ?zz54BHz*HYriq!47l^g9gSmozB2ev&SjTfHZLW@YX(N{j>POi!2XBqwoDy)QX%-f#0wNwCLiJch!G=QenNgW zR3U0&^AZyKTt4*BLj|a3ijJnGCzMCNo_He>15E&Ol~8^|zMW`Ny(`)z(F z&Ap3*ThWfh3<$OuW32;RHb|`qKFU&+b4Bv8XD4eWkX`FPPMZ9mn{m!=~BZOzW{RtC3%=|ms@Ysxn{SkoC ze+Je8bcncI2N(e*d7>y}IPt_2Gdn-yOjvhG!#YCoP_#fzEug0!(NXwDxHYAd508P@ zLHic6jU9`Gq;B21`tOFO;dAHNy?cj{Irpv{w`xn{lCLg&Elty&_k1*S%UBO#5faCMLt^hvwr^uo+?u3lj~IOW;6hG{O zqu>t9cIRvt$5bQkdl`RJg}o2s<|5EwgVpZcyZa8w@h%&8wvId+HOkJ5=%}EP>p1h#jk}5S^dvrNj!#nZ z!57Pl7O1TS8suB3s18xK&u1%o=Ey9lam_rPJ1&cv?6}0v#OyCg2WPdPyJ7C{axm~% z?8KR{z`5au8~E>0oPYlLzN0n2*}yMcv5?5RM`iF;(SdVn7W2Sf1 z?%TKG9kqEEgdgw4;l*F-dh^XUT|A~m>#djFO}r|PM&oYc{Ll_RN61uTn{E15McpDy z(E>HHK#ivxtUWMjm?QIHSK{&GKk#VIPW&dgSA3_Q-@FI>3DaoOvU z-*;e(es;DdnjqAVzof+Pd2YDjhW>b!$A^`{7qUj~UWpaS$CQa3qgnr8%E9AX9{0v5 zBJQY-BkR+rPZ^ex5BiYM^@}gQ^k0FBPeyoZ!_y$9AHeg-xMRn#n>g3GyNP4t>~7*G z`jZi+K)k@l&lyA~Rz{HGMbQGav_OeE{}SIV8yPEm4@yNOq3pJM$V*8)|WP}hNdxGUz{#ylVCLN6^bHOh4m;1`0=N4rp2 zMe=c1FZ9}YUEIBMUmPnWV{SQbjuunV0!!Kgb^e~jk`}l~zi5G?1%6oz6rIE`Ywser zq6L0;7O2MpBy;^@Dq5gufuaTSS-_n{RiE|eBTy_WTA*lw|7{EWKgL1&x70G;lK=n! M07*qoM6N<$g3XD#8vpi77~niBTxn+nShL8N{mtLkm+eun3Ns4>T!ny-RSZ>jp3g2Hg)_f@3w)=XUwM?zgg+7= z;tY?EmG4@(cs5};i;q>tix)7*7u@4ISWC_yPhpBETYf_$^Ccg}DuL)LeRjl- zS{D&pZ>iwIM{KMpjjXJ@O!RMFz4aIHff37mOs&B7A8N_)^>u>Y-)D6Y5=?Uu65Q$B z-`}5~0sk=r;{@*dVYfsKS5~5rHjz=71&phKv6{5WyLWIefol{v1b7@cMBoY@_zM?g z4u|yDH5?o*@DrGh*g!aB;3p37PazxOf9@h$Wh4I2H9Y6TgRhmuq@{tMN{05v#x@RS zwvHoF0T^&_2!iGx)Ew2`$?_Z8S~Ke#*%}x#yIR{l6oCV|@&lLF#*X?FuGUsI4*afy zRDV6e4_rTd%tA%+*CURWf>dhn6ez@O?Tsn8nOT`xsf3h&ICNcT<3RnplK-hk+}Od; z-rUa7+}4KTpS&{PRVBmsEBzwimOt z1`0X~{WC3p7yk3+e+&NAq}o4C^73&1+2kL0{wVp-1%7#ZbD%Z-hcOgl1F`&{&;EWM z#PTq}e+>BV+5GEMU^<1KfLQ+5G=!eS?mEoD!HK|0i@*Nh3cs6yoK8C7R=3Es-6Hc% zRRqD;Ow@5yoGV$!>S_1m-DjH}$|C@UKaIPdmyRy?av`DtC>sqt94!&3xK)rHi8hK2?`sn8w=9vkbAmsujx2zrS| zO(nNA)Cj5?XV<-iPA04}!CC7p z$z-PY%ho&i#vQlM?#rtw#veC)X+vECdkK1KmD~;djyRxe4 z)Y~G&|4zWj2h?KCy5YlOeJ5;7t)!RJcuX2bN?&9tGd$0#vTEFpH)aM?IBxH1t!Hm) zneVkdU|rm%!z5|kC1;N3$Ru_?x98sXb=C{bGtfF)+c?^Hl{zh-W)Lw!3Q4(+|7Q?( zxe#O$Ulpp(6#7~3OqO(Gb~_KHa@AVRR2CzI41l)3e6*1J6_dJ_9>%^5;GdC|he+<6fZ*m@F>mgWZNYeS#hbCOwp_J?x8E#)gsde6l zOg?=9TixrU%POv7AO4_5{-4R!K!PVxsGGA7v)!o!pO%EzzWf3!=z1ad*8Oxsb+$K# z+WKsFX7Z~%dDGq1jwkz^^X!up zdNAIn@J>g4bf(&$eZ=fO?uT;>m*dLuwuOnUNZ8gd2M#(l498HY689btazIXuSG4n) z*#BkKfQ3&hA-xy0x_s%YT4K8h*5P7zy1hPVJ&+(Gt41g0R!gh3-YcnFI1=476~0zC zVS;ZqsededwUyUN)g4K?GeH-6aUVx0{;%~RIOuZKCI$FM^Rq7!4>7;ii zaH2@noz1m})Wt~L&>+A`;a^4%43hy;@cYiHz<}N^wb~lE>-i=Z z9`TWtuCTo>e4X+Guk)r_yJb=GNM6sY6F*u^LSDOUNffGrMltXUJG0`dRlxlHypJk2uNwyI~4C_-gcSqfn=cPpI6YNWwRVVoiIsv+QAF z9EndW0tC;d4bR4v-m!RNCvPq>v=?mmgTQBV90B$X~2C9 zFeB!&h{v}8MmC*IrBo>3Hib6~7Zb!s0XQuA<4{pMvim#ogv|TPOu|1=NuFPoh;u{0(dZYE#q(OUH8pLWW z%$cUWnwtN$yEoK6WS4#NUa^TU4jN*qqd@GM0qZUK&IqEoDeYMw4fG{@qMwX*yZI~5 z9rjL0V8xINqc+9GosHMj^qtir)WLkgb65;AiB&NB4?`Fl79<dp{1$fnT$+LWhMKN_YGfDnO*z-KX7S@t zpo|p4tGqF=r#tmpsuQzjBbyQ|7@Jz-#pA?!^_EXH6)J^^(dIExK12@%2T`}mJUCk0 zEHY7oMUG!6C@Sh(&(MCP_pebk^Fn$tow_Kf90qhs1I0Hs zHWqK}Jr@T#yeO6NZ@V6f^Fh))ZZ5M(_;<%dI4CIECZF2mrv7Jh8Udl2jm2!Z=}x@F z#`48`>myAk`t3!P6g%Z3;Wl2~#Z#U>>P%Zs@BWej*O%>Ay>*nPE#XHfPWd*9w9CBf zO5DO)s_N>5a>mxgRk!gOAL@#qm`VLLAK4Pv^gB~!i|3jj!Y0%n%+2gEQmyTxjWx8o za3c1=V^hF+C4>%&N|&xDMd}pq1ni8!cC{t}7Z-nXyLjKyaK~Qou6OjOIF7>d#R(UsUvx* zjyvp8fW4_G;cP8V{%oB(6>#>pZgSPQfx$}@qJ43k14%59{?D9Ax?T;ZyE8w9ZVo$& z9Rha!j|z3Qt@fZbsWg?$OTYI%PDO-Q^Pl@VCdF-|ztcQmA}2~#n>pU=g>$eeF}KyB z=xRS>*@@`J*{p4yN{Pk{We{0v0+SAn|6_y~C^%=AA)h|I!5>z7ys}l0q;oVC@bvop zz;-jkB~uV+bvb&GJ&aO#lwH(MLC6@Lh_hGz9rg!5+!CL9jDXREKL+#gUUATs=}NhQZf~CcPc%BbskeqSd znYDVsG~(Mm7k`hK?3M?W_%wzw!IX5lU~zF4r}03_yCdml~Bgr2aGgf z05V6R1<9izwUgeT+1^juH0^N<--WW-Q%i=I1Gv#mehOC}6qA6QN!!lU*FsfL&r`9jJzGYqVF5{A%1mmcksrQ@d)JHiU`$32z*zT26 zo)WH<-R`vBS&~bJm^GYEYFe-L#+(6w&1fPoI-~WfsI}kUA@)v6_$uaHtL;}30A+-w z2wf*u0AR_!NvOFa7&Q9Py!v?*RXB`*UD5`?X_*oPGC>6XZ4dk5qSr8WI{<|&!<7b< zg{GF(uQ$E#Oc%YNgDrjo?Ez@3%H!a_e4_{l9pDlNZOc>XfDt)DnX~Ia_qusb=@?%1 zX)7nl;6&S9g4X)t2p{=spOTyubCEcbn<5lwYv9L zQ#xk;jwqGq^J~I|lX$;$ym3sjwYj_F#--;mn;c=m&YZ}fZn8=ZDG1m8JfTRrUN zm7-LIn7N^>Bdp9(es~Ri>QA_6`Pw+%yjlM#FT5wQ2ds(J9fBi~=or)L+v=1Gi)vxj zORcE}2h(fl6d14aXcb$Z>hI0L4cHts76etp0Kk#T2Tw~Xo%)CkYSn&n2)bQD7Cy{H zC?n7hYD<=mh1r%hABC3av_PYT(Kg!?52cz#f?3;9Qi>5F3ap}xupg8dDRzirj;zBG zDPRj!VLul&MiA=;b z;7r$f##kuI?CuI!LclgU!v3(N*!Qb67=SmTC`$oP7K&Zt_`vHwxwHx7ouTGmXXL$( za1XQ%g!kJFCg0Ga>zj0ZlNfM4Y(u^kCt_(n^ph98AV)x^wmM4Gw1Mf+)9&l3^xxkd z+@GyZ@ygFEUUc)^5U@2X0x?iL=K9)k)ZO0VZa1;*35OU}qS{M}eH!X0k3iM!9o_p+ zDwRzK;91Dq%O2so(b9$uB64PFZC&?0E^;u+*Ka&wuuoG>sBckVHxb95Bp}#$SC>Vl zM^y2mC>2llOs|1ex)62xnUJg*2&*9SMxs!ybtU|69G&%}b&kxQ%`9*5aFx;f#NSb} z-Xe{{%7WQ`sM3tfY<6lG;8I1xsLQ=>KM4;Pg3;-WqEO#lgb-1>4*CU41)~aY8~Wqo z5cRE*jItT7h17*H9Cv)UAGNQKtk6QK@9*#5D%8lr_D3f7uq#=;#840-YxXdRZj5g# zyg8nrE(C#+?If#dY5?0`=0`H1Twh7)2&4bo?8%T&a(dSEEy>Q6N&qXu^5w^VgYz;t zE{wUcSI#~zVu`Y-Y`H>IkCqUDR=PMQaVK_CUZ($RX%D?{$ZL@B_WNM7P~9FO{Rou) z7cz{(dyVPrXPJ4fT=Kv@`10faXzIRrWnvy)DyXvg)G6nG5SB;=uz^kx zW-vc{cx*%3=kQIpN#FnC&NCtSA)JQu17C~(Krae}5MasTRBUBG)WqqD5dJYX1P&=V zBd#LocUb7VAtmBhayhS5^9Id|wgu^CE+b^4g1!$T03|6kPyU}b+UmHUL& zFk|uM_xcQ@$&T*DbKjY;`**P5`$aC>W4_U8vPiY;iw zm1>`h_6_B|5sX83C-17#cYDbLj7*}0NUNCUKh zA0zZG96++TO_bjw|7V>4hd4q)z_If6ul-LTM>W!k0^pbm;@s^&`K5E~ zC}@z#@^g1`s<~&_SOEx`;wwm=I34XZG(@I_5FXGivc?y-HUb zmK!!Twy~L7E3zU4Pv=pZiJAm+NUVPcf=}qmzuw_-e)Yn+eRPE>jdxWny-Bn~a30OY zmqL$G#JF=cUN&;Kmbr!If2VAQ3H9@`7`JCzSvXQsRK$L_KUKfBfCn8e4kNasgB3QN zjLCQ+#}2=Et_${Pcz}9@@Do1kvlTsLmZSpC)l%f1oN-J%r@3LwmBROz5V8*!>Cy^6 zf&@ncnY9}q7!vCrKJb}Sxe{T!HB&kR0}N0wLTW6BG(cy0sykiMylzn~)iOWcgg{cf z$3jN)q)f%1P#GR?44uJx$W2ir^LL1L)T)JY!huk;cZzYhr>yeb6;t~?vJYb zDg?3WRr(#F`d;Bq<9ef|Y6^$6E}mqu-hoFkpRjn7yDq`c)>><3*vM+%bB_h-0E|%! zfIAYSoU{jer?N$_;$UDAFlfhKXrUEaZ64QhzTnPnyU;u+_|}lqaZ4djK73o%j_E{Hvn7=E3#ra# z9?E!Y)_7q$W}o*9{vi}>@W;(>472ojKNROc#vNW^Qf;ig=DG^ilusJk}VhyBoeMX z%jv!YaBd~@>YdW#y>N}`O%l8_vD7WEi}ZN;HxOcI*%+N*RSx>-O|ILos^7k$1Go3Qj^c(4 zlSbcJ|Hv>R|_F9U+IwMPq)&une{ z@a}85bl%a-PuqpE+e&hLT?Y=Uav+#^8kc1vzCQhU-NL5!mA{Hb5$pjhfQ#yJ0kqOsX2atmI+1yb zk8BVKOilRLTtyrT3equ1`KJMfb7e|pH7Ebhi3^IQ)qUf~zgynHZcB2JTTx1`^J*BD z9hZnq&J9G40fuf3Tj>ISx1217LAq&@v{qWG zMz4nt^rJZ*}t~19Mj1-OkO%u zWqR$Nw&Nm7A7wqpkruD(TJ^j|CF{2rBgzyhZGCh@+IQ;SG6L3d*rglAMD@hh!N3iz zx0v`|OdNOSBJj&r2UwgK3awv>8~3C$(wbT;n$}XrAVJk zn{(^8Bi)zV?E@s~`AS%KZaGWq%~5gnC~159A?s45xa_l8_{fcf6Ta~*Yq2_C;xL|| zNH$E26*x&pJvQjk48&{F zT^q-aat6a#z|&|nq6d#L$+QN-a7&cHkToectA-v*5}%!mf*(nQY@K{$a^cm8$QacN zE$-L4cEPNmSjANttfPo8>-gC1FOnnHLvrp&cw-R_#|^HkATmzoAU>wN5b4rT&5_^+=NcJTI`M;|=1 z_4lhEHo7rS{lRoK|AsM6H-AdUy~O($D?l3v9Q9HQDjoHm3Q%g8+@uU8=$;UH;{9fy zKnTkRaI;&Be$|UR>5TbhQ)5@s)>@`u(E5RMX{#Hs)O*ABMzVT)_;ntiv+9x~NWE=l zk-uk9^=4;+Y~hkz5u#9C<)35$!N=Q0Q(5=gcBf|EsNXXlHrC^(EF?J|gFnivDkreV zDX>#qwF_{%eZ0oJ@>ws*l)GV~5ocf^tWSxSDb3R%S=CBiKPe{#h!LUcr!gWuWeNxQ zWd*gnn$+>-iz)pT#w1Q_)qIsQR(tK zPIk~nCfBYAZqGMGVY-XOa9~6aGm~11tCUwe(%U58%tWkrxGEe@wpoTB437qK zt|lS)5$N~eM+#%0@R04+Q|7-3qLg#x5W8%y$l3GggN?wrUk;6J%X{6tUtUL$%+S*a zud!WRSgaB)lXmC6pc!&#BNl=N?T{a6vtGcy(1+lg+k5hi^^1Rdk9Qt$-FGa189Xie z>Ta-IfT30N=snSIPXuY7f%E&zyrsFvu50}Qre!H>pPNRDrXf~nOXU^6sR*i9|7kTd* z=!b$fwSi;RoPeT}Z+3UX-hn3R*?@-~XJUT3dF9H>NH%QnDwtUOZFS}@ou|~;K7(}| zTF4_Q*@;6yJ=nXAjD(yyd%Eu5jr+i!_ms>U!D)b;ZYE)o*>V(@CvCQGX)&gV?l>9Wj< z+)q!xiY~HDEsk6)Hj4t#XK`iSt()bR8Xj^Q4DcEz8Qh!+O+k4oo+yDuhExjI%ql&Iv89c z$jW;q+IZpYg301c8}6RN5kC7Y=e0y1r=!cq&aA^)$~C3I#M&dYKo%7JAQ0lsn1~Uj1P;0w3ix89=Qo^54#Tf*0YLF0F!xgex2d6 zkm6(x6ft(>x)vgtJ1woUGrvP<*;CD#ck(E zV-((|{X5L`KxhC^yST2c?mk_^=rOm+=@a4>SFm=2X9m8-yhY{LAo)gtQYpih4M+`! zOwtSs^!!j7n5f0YW&>cB=rBDSJc`A%#R#4D#QfRguHphVq=l&$=n5c-keKL6;T++F z@z#|tL2R>dK1P4+TQ-x0oB0E;&Fg4!=3B`9jeIwQ2pR0oL{D=t5p)M+X{U7Gn|Zmp z_sTx;8_S0$*nVbK&-@FCV_Cd{)Y=CQ+xK`(hUGGAGt+(8-Ete3c$+c#P}i{97vrVj z7y)Kra=@cyZ+!}?+2M>P*C`ecUWgS3OwLP#fBPLTWlml*-;aoJ7wW?ep{4Z|__qT= z4u@nbNEDZc1-T2ST#rLnENBOD4YPPKm&1Zt*S)&Ch|FPk6w$6fMft%B%lN39JC-OC zUKOmR(GZZs<*SmrWvxRK*ubMNT?F26_kd|!Fc>r>fpEvsmT}SJ(A~bwEA3OW{qi?y z2W1hUyLH#W4kR^Ff>kp0_whd$!^&a1&yNKtuLd{zS#+fK9_R)jU}pBcSoAaSD%<=% z10Io2UDHX#a1dNpMB9W`*vi%DPFU6S)Os3V+Nv9y3OcuDw76!HZnl%6VOuIi zJ!{MsS@r;ka>spCnJDxuF^bDP*Lb`JGiEaD6{mbA*Ofw!d?;NQB_r%mY@4K}`cAs^ z?zvx>^rao`nxVLE%!$AFSltT$RrH_+kxJ1GT23m%}4+Bj&)t zunxXgc3^l&g7oJ{n$BZ@96eE9O`2cpJ9n2$m+F+w5}IH17z^YsD4?d+Du{p0?@vj^ zWA_=~avT<5ywRq$9)sz$o~dX{CQ{1uVZy<)yncralUl!Ijw3Uu!C~`?h0HHJQqWeG zmkeZ0NnTqIzKk6sE8R?zE_daF-D>@6|G581=PRo^YCC(X)R^gZdePn501NK+Kuvyu zwN+RJKw!J+_ZLTlW?2nx2t_f%gbvIhkIBq+uviuFbHZGq&2Hx|w0DBxlWdDF)Kd=P zp5Mq@(GXgJO!lH_#+d*mXy-+^P!(vl;V5iU^&dy*W?0#jln`v^1-4f`Q<=YSn@@oO)0Z?T=D zx)?X~WH^5z5ur)WR~nCxrJnr^LOD<3UfY)6*h%sS!`&j^-TkC15DxuTU>k;&>z%$c z^L7V!6l``M>P3TrQ!N<`?du7?BlTntv_LW(!_Ix$7qs)yxfw`FEuWChC-Mepc8qU2 zRV?9@4Ah{zp)Z8A%seLJCX4*G6kHp142r+xKR5ryD|Pp}SmIdfDP^eNdVkP$01vII zV8^KyHmF(pTMrscq=|TX1>s$}3pRD`3f^Tpe*a$m zT;OG;yhmfw&cQp-M~z1CzOBWSby`UsvEd{nr2Niia7Ltd!iL}FoX=fP60{jOtNTI2 zCaGrUSHPS1e=Up(Z{k)U#c(B&k%CM`5Wc0lw!I@U@XeB(szg{_HglXyb&tnXJFXKS!E8*%bKd+8Grh>aIP{caNFkX#zsF+a(IC7#5S=ts@26JaO z{(QA~X=km`SnU8C{#06RuDhfV6bRkGO7m*=s zxqeKl4LRUZ_HAg;n;m()I8$(AAb(HYW$D0FYrl(UGw6))iDbThs+vq=X-?#XOgYyF zn)s+c_$?p>Jkl5PomtKUW-ImVhP<2L=8c;1 zpzGb>d(90A;HcDIYW5MxFbgBfJzjJvt~&cR{7iEP6j-?-mU@7>i)&3`iN zKxzS`m@MkhKQb5{cwYz3=Ep)Uo1zSa#P4E#M;AC#<63YnIY(uvx(T~ z)@_UgesRX^toCvdzuE3#@v3p{4l&bxsPzdQ7HQLm3uW}U%Aw(xv%CJXJ)nictN8Bm zr;O>Q;pE*tOm*)yx0|(P=7m-_=!FMvkT!0T1moOPQsrI1Dm7h^&SAzqOoi{#<_Vfw z`uKtU;XoY$oogAThPJF+NZU=@RCTUuNY(t0p#Y&Cihk!i@A(q%QpsS=ukM1c(ib^MgIeVz+TwKq$o zntNV{nbyw2<|+*Wt9#S2z@SLsCOUQ=A7olG%3_jVGt2F3UNgnQ_R9uuf%$ksZX zAuuJNWc&vwOQnzoXDL6nDOwMsw)}+DpPsZUWfL2-AXnYB*LPHV)>@+Wd^o_GmiH&8 zlu>%}8L^)a=EXX`RFO(avz}$JY(mgKeGC?{MrbDa3XxcpndC(E$OEYQh=_N2UmUdQ zH(MJOdi7>;(#^xW;zC5kfUi_iSvl6LUymB_Hdf*VVu{{yPKbQ zPywDHb{?1s)9NE}fK{xDOsX$eh=di_T22kScAo_*@@z-%G>s=a^><}AH_uH4@k@9g zu^myyaPciX7LQUY|5H1deTS6mS)4xyJ`7-)mSJfz;xHW^OsDlff?AftK>PT4Sjy z00;|it4}m*tttQlZhEXhMjP++Sct6JzJRpKqUN-0uBLRcQe~aH#zj7D=Bxad-9z{s z*R>ca4RV*r19&I3YBR-_?U6zyx}Eu^I(&`z4j0Rb@5;62N_(cX_@4op)HFbtiE+(G z_u#Ntkb9ntc6x}m59U)Zi%g#ZobNEeNbStlm6fPfwD+3LR2nZ=|BJYf5ZCJr#s)jQ zS$xO`fS>WZ01&}jB_x;^TmOv>D;H@vE%SG z+kD)a3?}1N+e{^hc8`U~;Fei}Z?}&uH0?Kfgw!{G4T}*IkMS7wViEJ%=es9Xwsd{X za8C8SSPnV^rqHd;aU;bPN`8OQqXBsjKFZsKr1C6w+d>V>oc7Hids7o-TRax?pPGzj zo)A}mj1%Ox7w2rik9EDw^tv+N8O;mI@Vfft45&W7gyt4MH4xaM6rVm?A3(pDr9dkiNAhLqsgwSEoCh0mKH|03m+NmG9uz zOBv)!c?NLQ*TZx=v4GTbWMepEM*^BJ9k&aFO|W0}cx^%W@Od4druBm^vEDX6M-dwa z=2`0Q27IMUtP1mXi3=}h)|#VDs;fiRpb3gA{fK7YQ&NZP6=57~11f{{Szs-qkm4nYo&WXq6V z*uV`991SUO2xjdN_)Eg)ZiHn8Q2)j99h)JzbXU!qfe<5*S)9RM3iMD&@pm0H@*07FOA{xsk7}@1?dqRDv3-JQb${LAc z!YE*}N1(|Gl+T@2c+V|c=JN%UnAMD2Fv%^-8XfXc1AmBqgW*1v=UR?&Vr!>>71OxI z@^$l#Au-0oTLog0@9S3kTRK=fJmCh1(;H~{SgG=_|E)-SNVozbKMtg=rwa}cK_R1; zP|AEqkXf@<7Raq~C`)*eKurAG1VB1_WTBTKkoHkbZyCisW`Wm4A|dUrryLV)3?lcM zP7*UCM2I*6()%zs5WqmzTBi}uR!7Y$Cur_gf7d39GE*WbJ1eQxj(}Mjmj#g#w@0WdYM~GW##5f zN6hg?nym?G)WK4Kz_0u`D-I7E0n8m8JDeV{1U<_sjSu9G)`u1_%<5)qQVNczA)`kOKM zJv3>v8OfZW%)?Ge6_6R-Q}_IejV#+jZ!LwA5SuM{#jk%}}O8<~a_)Qn#gD$0*{ z4ajPus=5^@QO=Fi${m6{Y4l^UZ)68iQaB}rH(oI3j;Mgk-GkvY%7+bCP$4`~K#uV7emCyCKpn&*3ed9gvVn z;dGrK>L1K{MDsA9tM>NCTg`@hqOAO$W9IaaxYRe!#4Je3 zR-gN9FP7aXkYMpjauq>jpL*%m2#$5JBnJipuZXH%v$vrbPNIB%npqQl@Wv&Qvu%VG zrEiIF)m@}hPrGLXTSGd{A;8JmQ3q^xL38@X5KHteA*05(;8z!EvLq+kF7w8l=?-#B zIVFF9nIB{nb_XrzP#~uk!w?c8#P4WknYCL!*8*yR=+dmN}r`bZWk9IwP!OXn%!1)lu-f|D(+*l2iof?DBZWF zGa+_Q^#hZ8cHH+HvQYc1w#QYQgZoM7R}ia&*`0)|UXy_h3W%#}JKQB<#gvme2ji3+ zVn)4qWt>0Pk8kN28S8i!T&b%-l5do@@DA7wMN`mYS@n=!cDrCybT_byz34e+`@x~! zI~cw8ytotHwJ3)IT-{mF|G!EQ;DRVhWXl2eg;b1{jkbijd)A|wz~4yXkxp+ISX(S$ zh;0U`J?7@{m6qq}#Mw$1%auB{nAJEg{$?QW9KGZ(;h)*%tLXq)HbiJMO_t(3S-QeJ)W>Uxz<3Y9f`!9pAhM(j$2 zgqL{8cOX%FY#tU%bZOAnQPz&L_{)9@2yDV~A_4n&T>h;l{vAo6OTpQSC%^CqtBMi0 z1@hcUU(z^zh#OlMkX*z%Aj6G%rC!+?Bti8I|CL(w!5FQ>-7_9yqgb`sl^d=_v)K}} z3ds%?KE>L(2*X*Hl?rM*n`YvvGcD|#gm;hM3(8D}$qZzf3y&FI(5mZKZRY+&aY%HLABsp4X^#)l&?rzmG~s3tv1zPLb3mepxh(imP6`x)ogEZ@T$8J zo!yJBAi0-caIG{A(U2VNsbFSwP$Q}lETp%APYK^&JBf*{6@&h?9&tSuoC}>Di>B|* z&Z_60Jcuh2vjbL|h!cC0+!M|^>dt&oO^*uiKLvSEqx;uOn8c+x=4;kzTQXO_d16uh zhr0q!LD}h?Vl;iXS1PHk&^qIRI@i?h^%oyLpRzEHb6R4td3k^QlE;KpUDIC6}%A&T6#&Ui@c`LI{SI-*+{YYrYlaEm2#6x%PgOE z0%=!VqqHHwI06 z`4*4^rW(z!I8FT4i2f#;T@dcoYPz~RC&lxIINtW};caSL%4#lme;15fGQM6JwoX)+ zDq>^IZcbzJrKOox$!LWp-kB9l#G5pgV@r_oJARvbXBG@vd9F9#_;8vl(5@(oOLBf& ziCp`)1p6-oxhC>uOlRfq383hXs4!kMo1b}UsC3ZtldJPCD?N$H=5{_7VUu>8F;niE zDmqL=z18a$oV8gFxuDKusR*#ln(-|6WluNSBJ-Cm-ZGOYp_!W=TkN}Up>V!t z!<$e_<2gv>{i-)L0w|(iS)=+m?Mwo(<3MPkJgs$vCb{7)&4+KD8m$T@G!sp&Ho!(( z35YMQx~Zad0KKDb)PqvI(Rt5;n8&6XkV8OJ%5?U+@J`o={HN)%JPd4GP7@cSHrgtr z<7j6D4w?;-Ngbo{jSpJy!Au&}y-6Jn07nk>MJ6+o7rw2gM8Fh0QP>$P2!prj4ky$B z*&|9uky%%nLe8vx<=?mP5)Q{8JJDipr;iI|rQH3AiF#$=FejtFb9Zf+%{k4qCc2cT zFq7EJLv8zKXFrxgh`4A)8yY{Q-F_+dJSi8dQ$PS@04Gekl}6I9k)B|LbcV$bTz9~} zB^74JQin{+YFTo%$P^oSMx1(7y03*_dhl3H>lKTYJN|l4?&UgToa6@2>98StAb35u zDYV&Q;#*xJ?EL z(l|^bZI9QJboYC{Q&wWx>fWs2x6nm+(2_WWm$78lX`QZU^&)-@Qr!a-Wk(3OHlt_8 zTsi`O`b<95Y6nGyx<+Y9{wC|Wef#AQOrDh&)Uc!WyK6ZLp)}USy<=rf^p6adGf1BR zG|YwA=nDx@Yx6@_taaXlc7pgaWd^%gra4XGl${rzPxdyD3c5_6{dC!L0hhHTbgTH& zJ!OAsb_JZAU=IJO;{lV<&6?8np67SoHKSMd$13f4&Zu(X;IiQM^&$Rn@5psiV5Uq2 z^l9Jlt=3=w{)diAkqgu0j^ssdtn=ekUzWIfO_-8Z8DH9IGg9K#*8=b0d%1Xe4W~)8BaerI73gLM0Hwz7U!yd8EReo@l<8!{HKnJ zhN6Wf^Mam=ih=P#ic{)5!=Al&Bq~3a3BH!yYg#6Q7?@QWw{@jymggh@r9#7{_9J)} z%9%7Ry!q6C^H=!ccEc5hJy-L4`&wApuJgS3XFy&DwxSWrTcR^=eR;I*m99k}WheV* zoGUVq;xXe^Eaz-EwE}F_s@I8WM)_x*cd+<~I(gE&-DY>yrVxc(=jx(^6hJkwib_^* z#_wt4g_MlD3JCtImOFw>iF!;U=YgnVK|h`k`&uDhufsh+3MC2~afLqFGZNo`MnHRd zm59ep8iJ$g^vl9hpeRB)%28FNUZEGcQj9Y1QV}C3n82|2Fnd{0O#EXr5Kk=RNp3Bl z{Q9{E*d%v%Wrp|NrB1IP5F&v~DKU>;pRg68T#n*_^!(p_&e!8Py)+W14+)&GEKYH!Lnp^Ry%9ITP4F< ztmK!J1&#FyVylZzt+ZVFX_$0R+T0p_SwmOTR0HZ;Bc>p@PuTz``Q=@jO3`S#=Gw+s zudxIwH+vi7M2owNaA8la^=xJHhlVG8BreNcf;I;RMfZ!3i}!hj&leA(r0(;Yxve#>`y>vl+00RlapeeWsy{tYG&;OEl6}0OPH4ks zrX?Vox?h(tt+tU}`1{Df?-2D3E$iS=`$b2;cGYH=l1Eu{+|Z&JhiLaeiFb5Olu`zNO0BL?Td7JfoDlIC%fe;eDe3<8VfK3g$1OA9RB0n68Mc9yv`jCx zGeq$G!kRPcA!6fhVwDD2c1E)2y)c{>x>!Qg?d};6KASouzg!y1YX-Kc>*{b*Y7nRF zy*&T0VqN~HkC~vqh_4$nWIq;vb;%t`vOmw(`a+TTCq6VuOLD8b7tP<_w4`OgX2!nI z_~|&Gx{ck+UU51)IGZ=z{s`%R^iKT4ByBPEyBLwa`j4v?!mvl<@$mMpf!h|0EE?9z zL8_Wjx{lo9T0&x4%S>ogL5Gt5c%lp>Xl_{hZ$1r6Fs-#+dS5ML?(Og_BojvAH`|P! zj6!liNR*V>ySUD74<~(qlPY_%W6{ioI_8_A^DEYUM#Y%_xRntTUC*Q$?xa#>ZD`Sv2oJv04bQ>% zTYk|IdR89rbenpzHxJ?|IJA$}u)es`7Z#eby=@_U_v%;wl6F`Y@TCzNU_Vi%j!Uc|C3@3(d=SwDwW88~`Ub-w&E10&`9&kCiL|~iyOHB4)=>~J*%_aT`);P(0tL9!% zXBG8levBF8=3{d-bTgfWBGD2`TY7qg-d9)i(;<9BaKZ=)H_i9_W7PtO3C$4l=8;HS z%ck{83|A*nm~J$Rn9shaeN?1H0A`N!MXVWrpt7>+R6V77Q|3%)=v$sai>Y#!Kq-B6 zBAl6LIp5^|8E^`Op&$_#p1U17n>h`w;?Z{9`hW_W$y7*-%`nsJV(g3_c7wrBwkL{O6wq>+hI1tX#k(ng=du3A>DlJk^e!25Ss$z`AJQTb0bc4Med&~vcPdf;aMn=XuioSGC_Fms%d$h&b1QxQUDwYuroL7`Cc5mDa$_&s6#8DH zJ3O7*oGhuqw;xo+3$uvBejB9m3mgxW~eEp2Qw{8!SlapI$*i?(jJ}z37d+Egc;=dh4fhaYkjvKjUFf9#1Iuc zC$guV$-^-oL)uSQv7^zw(O`9yTIJ2M+R!`~jeGAcYXEJn2qU8t8T9~oz?Ke8nK~cN zciv^uv$m_GLj}JhA%tQU&|0_NJR~);N^p{BGtL3jNx1XY3>>Q7sQ@R+J{pxE10fA<4|Z!3oPN=+{dKr=U@TPTBv4av>% zzw_Nd#GuFH8|GqD2WiZzFL8tOPd$V)I==cWGm)!`v|QuMF(~1 zjnXaM-Tkb6X6B4%zQ5-mUjn-M+9q)I+vz z!CQ3F|Lj1v1ui|<+Q@68L`OjN#IEMe-(GI-He}*88iGsLvAsK~nzBBRnkhz}#=anq zc%&T}c<1y~=&xUFbsx7EnwuT==lP9DfKy|;@hLY>W>U`c;?mQ&13#wHapo@u4Q3jj zF_Cm1l=_9(toD6Xt%Vez3tW8M|DL(b4~sA%8F~W1&?)+xoC%b|^QN&kyRMS~;O4yyH9$TPxzw0@dYtqN__Z!CPiE<&F{H{@Q^mOq37t^aFR)9dpdhtR1w{ zZNG71BFwSn6HgoHr_7gjhTu);w!K44CrQ6WT7~s8P+?w6gQC`QPKT}c%$~NO%{AK% zE8`q`BI=NR!qjsIRzJcv$KGgatpb0$|8`+VQ-!oSwRXF~+meaC6`_g0!kd6qb{F}z zos?E+fjc!T%j87)sV|ZQUPHJNhcBi?1n#lRU`M6W$c)Rz_=F0S)-I$ilkYHZ(S_32 z3giv*{f?cu%Fe=_ZPUP=pX?-Ki~jJ{$Cd}oYnTK;)%}uBu=OA>k__%K7B0`#POAMc zX{~}m(9Ll>WNJKlM=2KLez>OTF!o^{RebtTl5DqJHlJu+gfx)%3@Ui<&iwsj^8025 z`6}lQ%=AEXJ~1R!2DK)^-*=QN7?57>1{=^;YaUGDpIMOK8mx7W|LNZ5fTUd{3U5cU z>)#El1b3>dxru7pT8tDio(QbH#$$}BI&lBZvN8`d{`z}teF*t6V)UOC*t-tPc$=xd z(lobQm)=V)1G1)IOhOws3N^~_&T=TVMk(@d0CZ=2Hjkj z=7;&8b^#WGB8d*nc8OLC_cBGg8DP zNd9gdZo1hsMbRL(TCST}1KguWz|XKT6ZBD->UNE@`EV)hrJUIC#?Oq#G-SN2u_s#2 z888|7<<(11ln9dVEP>h1w<1mEhjiFOfyOJx zM{3Q1Y#lJXv+`Zals3}mi*epWKqua4^sU`{yem`ArsOv-DaLUcvaoMl`$g3J5au!v z70$(*2XEQGJNQEFm=~6-G(;cd7MNz{ZDT@jI^-6aalB(+r~Q0P^by%jj9g5QmEcjw zp-AX(_mc_B^054BwdT~bvR}dP)a^EdQq-zB64dWsUSTPQuA)e&Kqx>e;~>fw zDT!~^?XcgtPX5#w=|#r`0GxOP8Y6A59R2!M&#{#-wZtsF zr$VTvjG@Wr4Nn6fbRb;6U5xgx)6YhA(^#fq-c;4iXVSE=Fsdxw zirNcC!9%$evPq9jC+697{`id;e(@765{qx#V=V9UfWr@?mmt$P);$K+qjLwz1F_v9 z-u;14oe zW2oT%_G_u7(+TbUVsS1+gh10@h&pi}N{M+7U)fRhyKogYax-6@i2ZfO753iRH}jA8 zF1NCe|Be?n;lm4yN}8ut-K42jh*(96Ca5SkA2?J^;m|RKmymKHQKQ9tDlusOXvZpa zRx>9TP+Q)Wr}1=pt>01Zc=}(FzbCW&J8tKygA$geU>kGa6}beqUQ~z}E{^p}RTLb% zms(GR1K#d?-d?$+fBj-L&C(|(OJYAPJfF`2+)IKS=!Z`$E9z{DKI9+Cl7?o~fxaZp zb^w>abXbU9Rd-Dk6b6kks?2;7$kwr?6F{;yc`!AaSl%TJCe~@F??@;ANX}t3RN_It%=^ zLc9qxO5DebsMJ59^gRHXah0Mx{}?c0kzNCifUPUap(&$H8y$ZCqgjUu%_iBt7B^Od zzNch_ge2Erl{k+mBJ75^yC=f<4sIS>8B}(i(Hu$`b8zouh(YIO{f}{;z$ru{j-SD*09hxGDZa{pGD>*Z}>1XiIqijN@ z&lBq&dxkt?JUS4+ujaa2>XVUl(fm_TiNuCbdbP3lj zoDP-ri72KossrplD@=C7e#l%UIa;Kjvpm;KBAb_@Q4d1x&g9wr_V<$X>LH)${bI+o z4HNMzOGO#=nF$R#{l&&OXLO+Y`62w^iE#7Pr$^{?IU-`s;_HDey?(na80t9&9st#H zQd^vD=j1oL)hs@+vgJ;TT&bX|*-1E|XlUzD= zS$<837rd3AEB|)%-DG5%NG|S#dv{u7U+-NVm*C`zt5)<@mfqj5R3)jv&v4Hm|U#k#8c3ZFUnxwko- zn|W-{j&3Eu0sN<8Pca;gWbfb|F6gj49&Jc|;o#!t)*Gf719F&A8uC`9#GvrMMO)x6 zY~&Ab4DZ)E)CBX3zHYN-S?pAmomGZ!gEDdp6X42(@*~T}q*hoG^s0QUE5fmTDT;=p z;w#PIdbYEPhefO1Rf6?QHLjQ8TYRZn2n`7}jEiX88N#nY?3V_0FHXh4+FdRnc3TG8`}+PO&IL8St3tBj+NL zI;Xw)9^9bvjY#;}Gqh&VGPJn8I@NkW0f*&Nklt0Q8)|^9!gakac<<5(%+3?V-+1W$ zXmzeoSkDjbItE<_&A8GK*o5d!2;j))dSDq(!S{hm#)GySnKSg@_m z+s>pYXwmW(!8ra9g6a1sVRd=orcdt9zUh z1tXm{LowmGd#dYy)f-XYmtU}M7K84gUh5jS-P&Z|SNh%GJ{Ul4?LQStzwWI`1CFVBv_vRLGY2{cIGLq)5_Qy@|X!Up} z91*H}^%}-izzWi_hebAwpTo#PUy64O=Y7b(@w~v;Kw^L&1y~UnQLL-YaTWlq{4dBv z=xZH8grpUCy0_clOieAopTZWJh|Omi!GHE`&VED=CK0-Nd_2T=EGsy856dmTjD9GleLKW`+n_~-+Af4ppR(Taq>#y!T zwwtwN+&3PPQSZb6uZS4Ax6qBBq)2q59a7ZOQw?#m*29{>;1uZjzarWCj@N%jvhFG# zH_O0SQ4%0g~5rlEr*^Ai-xqDH&dIdo}}g8^$yYtfQ8R1yB^><#bAD zk!HOsOSklyJl7&T5vaxC`GnfQT+5B?=sNytw3*ngQqvh-5$7&H#=`6%5hcDY-0Mlp z+YtfGxt{UCfA$rg3a7ENiYQrBkiz7K!ax_tZy;?a-lb>?&g21{oKgu^Q2-ueFBfUs zW%Q6_gau)F?+adK1N7aE+V-l{CbQ;akKIChNgJet8Vnqg%Qmt(QJVdNx+-Xh1WV$$M z2igQ3BtB0}&%lABnmR1pK+LXS-(_}?#h09zCkW>R1dr>w!yUur0Q+B@$SBn z{lUQ~+USaxtUA)8zqU9)`nj(YvgSPz?y6V~QtRXKzq1oi=aBs9?%?N9Xev{v|MgAg z+-P2Tp=DjGX607FBZ}eoNKf%FYVb_MkRT11di|cQYh5*GHH{%tZ#WZ|(hU=tXV`7b zuO-#g^Pe~nZ3G+TXJ(E@$cyG+nL2|!-x3`Rry`M>c6dHU}4+M(QqqR42 zN9Y_ftRhuC>mp%Ag5ET|gAjdDNOa1dgnu3?XHd( z`Ks&`_g?|BYkBHD%Rp9kvb@%_EbSzR#2hxkT%r7T1p;>I(~{kUZxj2%#yNGOqj>wL zr^xze*84@)#nYy+((VTvo{ClC>Kca|i)nouZ-xa*mFsGZRNeeZ{z3dAS=;YU`a+b{ z#}>!Nm+_JdcaP@umbRktCLW}o_Tzg!!;1p=u)`@^CQDzru95DMm1ztEijOW2YSMjq zv|y?UV4C8zQ>k~f^nLw&By$2(-7FCE70KX_d2G<@X!gWIbW;KXxA(B zv>}vt6puF{?aaV$gy0Nz!Tb2(_dBvMOT0Av)}3&IU!|a0bqa`@3+n>e=KITv?fB{8 z1K9EU(jttR`S~i#S0HU{N5-Mims5)V!t^D1pLl&+vDujW-=r82P5@YphH@qQ+X6=V z=jB$hPSGl61QFh9Q-Sbj1g+zNXlGLehh>d`h6IBYcvL;ftNBml*3u;Nn`-?-z2$mQ zEyDN0$u){7OZ$|{FiU#SYRn-6QO?!jOuq7RZwT~htulRP+uvPvH8gJ>_77*GoV`-f z9#EI@XIvPGAZ-H)TQtp4u2)f?@Soz*DeR3E8ob&{+ZdK9sael;8hn?be}y`1XyBLl zFXm|cv4)9brX62oI@n724zDN zIj93rI+*0TJjHBNMBt~dd$GG54dEz`vIzpSgE zZ0Y?&`V#@!g1uQD$!b@zxMU6^!}IF>imgdD`a-p!76I8bRTK{2CnFP_v4uQdl^=+K z^Wk+%UugGTr&o)@M|v679RAxF-~o zm|n}>5$9tf)|N9>jI5OFpdQEBt;#BExK=sOe2QKafKoZ`Zt)9C~XM&Z6mMV32j5 zw>e&7Ay2>3EGa#HS76aVe<6;sk2L$j*yC5%$%Rs&*^_3D{K?O?f*YyQSDLEZ(!&+W7S z1whaTyr<)C)o&Q-5+s+cQu1?x0MFV?cGvH~DEr(AFiyq+EJV9`b0k~6VaS;PsF&2b znZD%z9oGtt(3JE9RmrG3ji{N)U326*UUPXH@^2!gqBwTbs`g%2;5M);P!zt)% zL2+q{yrcX8`u@8E>r)V22S`O@wmNo1Ng8U3(oyI&ayjjkcDqLPvfU!lwb64|n~uF; z{;sm=4yuf*mw==t3&aCeK(RomCD5R;VwXvekvQXmu7e>X&B%KZ#LM?kzbCePv4LBKez#JlT!(@`oHqi-w)y6DmJGBKn>!89_== zjFXZlVm|UsPHC$4MU1^9l%(qRMDsd%A2_ghqkk^m5^`E5B|9$#Id0`0cFJ@czRjIC4|I)6Ff03!LJkZgt2 zcX%TBmB!!JIPk(%K2*wJzM^^ZP)zI6yj}*ci1JO@$Mmt{SdsL7SzHf!a#5 zi7UPMN8AFLnlgcFtWZ8Zn6w|DOnn8h{Rt~YM#PJM{t-s})gUc5WS%_8!J-H>{^(91 zLYMk=$6hDGY?kecU&eeaE5C5JfJ2G@m*$5>5eS(Sx*wX$E5lHmk7Vu#-gAH`H(wvn zjr`V+Gjv$C;a#Al*n5Zs5vG;{`&PN*E|1=L_sfS!K4kGVRH<2-A!!}z0Y(ueBQV9} z#4%v_^fL$x-I;6xmdSr>Pq6%DI2xI%`;;Ar0;v)NA6LEXvCP_9kt)Y>PH$sit>!9F zZbE0Dai4PZs=1{sEFAN{(PGACO-IBDNCGGnt#6R0SW+k3gzx7%vBubSI(DdJ{}#N4n;YO*}x z#}VLuKv(wkfo{|)0y_5!CX6R}1BDg-V)1_yK3g_y&=~zyG`H%ZzAZ|D^~v{h8P$Vl zg?4In#;X6_lK++DN6Mvj$VI-LWFVfJVBh3#dqR-?3|`*jMg7tDF(n7*%F_#RBhikr z3H)jaWBmg$+!!*Qs8bUhgYuP!6a|y+zly|jY*O5p|N8U`hv|RtZ@oR@A9(0p-HO%X z>N3>hCbv__a;cnqzxDf47x(M;PZPRrJ8k*$Yr+?t$9nZ!6yrISj{mwkgP}s@&&)lp&k8HY@mY&cw#Ml9 zEvV)|2K&k^s}ux~4NuKR(?tW&gW}#|7*-4zeUTAp{q7Jn!I~Llwd~RX>>y-qO25Q ze)-$Kq_fZg0L#Ac2vZHFoLQ=M*8S1m43baQfDG6%#?YNXQY3fmGxndgLpGbl>3gu@ z7^FM_(sLAhMZ*ooNCI%$-(KPTXDZuMBZDWGxvPEk->&=`mS(t?>t?f^Q{)T;`%Zyi zpR?xK(GsQh?Kur)Qykdvj<*s0_myu**Yd$i^{?9k{uI)~F}82*vOl==M6sYE*>6YZ z9e_b4y)Hr@^cy($s30?HslgM}YS7`7 z-{LD*3fab_`W*sVFQ!ROMHX$O%!p1Q9tY@U5q@PRwCf_ZcbV5#Qr^T;^3xZmz!yGh zwKG`?>dnIpEM44128Wi<7`*@lm2U*BoH2^?wyT4OKde)uxg_H4pi(#tuHiH_G&KBGh8eG50O<#HIjaQyhM&TP&}GCTnO+<3mip!Yz5LT6LRCWd?Jem>{q;mQ;4{2T@bmOafuZCiqR z-GSN@3~vhpHY3ASjKjJXic!{z0Nag^dLXDRS1mWZgf0s5`~+1IHZjVH_ex6BGgi$O zsqRNXHqDqKwbK$gi!ga1lJ>JlH=uxJ4(t&EWLwbBUMtt2uj!(cE?ebi9ry!!N>D0M z3qsZEjBuVlQqPOUf=rkKv*g(V0H9a^+N|REr3SCxXmRO24pcLV^1JT9Ht74OAMtq~ps_&P4!*KTLl8s~n#dkUKJZYmFz>n{L`%cr2Yi9Mr)2o2V!v z%^SqF0FaAuE)8IFo1Lw|4QBys$Oa~X@T93+2975MiHfI5`Y%Pi?M8#HvovNJQs>8? zM{T7rPBheg7E3z=;XM3ufR zCZ+B`)We0z*a)H|b=$+qhX;ZBH^|$B2fo6m3Ta*_o{7Xn2%3{Z<3L(zW>Q(rvTdYfpvvj*t z%+=35n7*kh93b+*%!sJzAqA_dic6T{a^91RV%)iu{O?d5AoYBKUe&r z&*$#ED5CyrTB4iOCKSJ8CNLoKK#C8qHO2i%dOuwj+x$tZcO*|=`^+Q3JzS{d=td8v z+Tm-M^faJK0=j-Ri+PIbGI1{OIGm5uIeO|IM2ii|$WRI`_C3U#ROcmRFJ`1gkkKnQ zew3+K4%C`FZ1deGn>fB%=OLYjhl*nGkG|pAq#}zBJuk!TKPBn!Mq2E3iZP1EW6jDk z(8^?1DQO8&p8hsIWIX5m+(#;7r~(&5CtXA!A>t1)Q%efX@CGD`4N0d<{SxR;4W_#j zZGl$N{`EwmdK~NX^$pps!afHHA7%O^J+e|P^ZH}>$B-TyUxe?p#HgD~>O5UY5S|#{ zIDX(;JEV&D;3EsoZW|G*YfoO^!|=KgjU9&A}W|u zp3wLuzH~ibNeSL5`@s7(t2zi~uo&2hJXt6!oyEe3#z(*qG-|*oh-oNyv#0mc07)+=o#Pf^c zxzL8%?1mVHdshV6emf4;x8`z}V%8H0ZNU)-OP5jB=Ave!MhCl2<1YuTNvWadnx=t0%?dt4( zoB=FTzG~lFY?P@i8D+sul;!8S80$3)d<%ifDTx(sel`5N6(fA|zbNVsB-VIH*Iy$s z>Mis@gUan>?{+VWZa@>Ju^0GFnM#hvq@ZQNki>>bI!3A%qxz95VdDx)y=#de7a zCr=;gh`kkglJ0X6=vvcONxV#Kvg9xM39}Sr7`^gcLUd$WewclJh PqCIVuq0lxx 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 E0NOKZCIA2c literal 0 HcmV?d00001 diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta new file mode 100644 index 0000000000..3a3610583a --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta @@ -0,0 +1,96 @@ +fileFormatVersion: 2 +guid: 7a4260b05936b43349341676f94db6a8 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md new file mode 100644 index 0000000000..946c026e82 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -0,0 +1,32 @@ +# Multiprocess testing + +## Why +Multiprocess testing can be used for different use cases like integration tests (MLAPI + actual transport for example) or performance testing. Anything requiring a more realistic environment for testing. + +TODO use my doc + +## How it's done +### Multiple processes orchestration +todo +### CI +todo +### Client-server test coordination +todo +### Context based step execution +todo + +## How to use it +Test players need to be built first to test locally. Integration with CI should do this automatically. + +![](readme-ressources/Building-Player.png) + +Then run the tests. + +Performance tests should only be run from external processes (not from editor). This way the server code will run in a build, just as much as client code. + +![](readme-ressources/Multiprocess.png) + + +# Future considerations +- Integrate with local MultiInstance tests? +- Have ExecuteStepInContext a game facing feature for sequencing client-server actions? \ No newline at end of file diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md.meta new file mode 100644 index 0000000000..5ef9bc8c31 --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 39b2fcb99dff6414e8f41b93f4c92b88 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From b1656ae5e159e447633a3ae1289d5c2ac5913a88 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 18:30:54 -0400 Subject: [PATCH 15/66] consistent naming --- .../Helpers/BuildMultiprocessTestPlayer.cs | 27 +++++++++---------- .../Helpers/MultiprocessOrchestration.cs | 8 +++--- 2 files changed, 17 insertions(+), 18 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 3e9d4d82d5..94bc5a7dcf 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -12,14 +12,13 @@ /// public class BuildMultiprocessTestPlayer : MonoBehaviour { - public const string multiprocessBaseMenuName = "MLAPI Multiprocess Test"; - public const string BuildAndExecuteMenuName = multiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; - public static string buildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); - public const string mainSceneName = "MultiprocessTestingScene"; - + public const string BuildAndExecuteMenuName = k_MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; + private const string k_MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; + private const string k_MainSceneName = "MultiprocessTestingScene"; + private static string s_BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); #if UNITY_EDITOR - [MenuItem(multiprocessBaseMenuName+"/Build Test Player #t")] + [MenuItem(k_MultiprocessBaseMenuName+"/Build Test Player #t")] public static void BuildNoDebug() { var success = Build(); @@ -29,7 +28,7 @@ public static void BuildNoDebug() } } - [MenuItem(multiprocessBaseMenuName+"/Build Test Player in debug mode")] + [MenuItem(k_MultiprocessBaseMenuName+"/Build Test Player in debug mode")] public static void BuildDebug() { var success = Build(true); @@ -39,14 +38,14 @@ public static void BuildDebug() } } - [MenuItem(multiprocessBaseMenuName+"/Delete Test Build")] + [MenuItem(k_MultiprocessBaseMenuName+"/Delete Test Build")] public static void DeleteBuild() { switch (Application.platform) { case RuntimePlatform.WindowsPlayer: case RuntimePlatform.WindowsEditor: - var exePath = $"{buildPath}.exe"; + var exePath = $"{s_BuildPath}.exe"; if (File.Exists(exePath)) { File.Delete(exePath); @@ -58,7 +57,7 @@ public static void DeleteBuild() break; case RuntimePlatform.OSXPlayer: case RuntimePlatform.OSXEditor: - var toDelete = buildPath + ".app"; + var toDelete = s_BuildPath + ".app"; if (Directory.Exists(toDelete)) { Directory.Delete(toDelete, recursive: true); @@ -81,8 +80,8 @@ public static void DeleteBuild() public static bool Build(bool isDebug = false) { // Save standalone build path to file so we can read it from standalone tests (that are not running from editor) - var f = File.CreateText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.buildInfoFileName)); - f.Write(buildPath); + var f = File.CreateText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.BuildInfoFileName)); + f.Write(s_BuildPath); f.Close(); // deleting so we don't end up testing on outdated builds if there's a build failure @@ -98,7 +97,7 @@ public static bool Build(bool isDebug = false) // will have more connection permission popups when launching though } - var buildPathToUse = buildPath; + var buildPathToUse = s_BuildPath; if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor) { buildPathToUse += ".exe"; @@ -106,7 +105,7 @@ public static bool Build(bool isDebug = false) buildOptions &= ~BuildOptions.AutoRunPlayer; var buildReport = BuildPipeline.BuildPlayer( - new[] { $"Assets/Scenes/{mainSceneName}.unity" }, + new[] { $"Assets/Scenes/{k_MainSceneName}.unity" }, buildPathToUse, EditorUserBuildSettings.activeBuildTarget, buildOptions); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 7f9f84d82c..7977f9db57 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -8,8 +8,8 @@ public class MultiprocessOrchestration { - public const string buildInfoFileName = "buildInfo.txt"; - public const string isWorkerArg = "-isWorker"; + public const string BuildInfoFileName = "buildInfo.txt"; + public const string IsWorkerArg = "-isWorker"; public static void StartWorkerNode() { @@ -19,7 +19,7 @@ public static void StartWorkerNode() 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)); + var buildInfo = File.ReadAllText(Path.Combine(Application.streamingAssetsPath, BuildInfoFileName)); switch (Application.platform) { case RuntimePlatform.OSXPlayer: @@ -42,7 +42,7 @@ public static void StartWorkerNode() workerNode.StartInfo.UseShellExecute = false; workerNode.StartInfo.RedirectStandardError = true; workerNode.StartInfo.RedirectStandardOutput = true; - workerNode.StartInfo.Arguments = $"{isWorkerArg} -popupwindow -screen-width 100 -screen-height 100"; + workerNode.StartInfo.Arguments = $"{IsWorkerArg} -popupwindow -screen-width 100 -screen-height 100"; // workerNode.StartInfo.Arguments += " -deepprofiling"; // enable for deep profiling try { From 4ddcd259990a080eeef2072d0e444272c3156fdb Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Mon, 5 Jul 2021 18:54:54 -0400 Subject: [PATCH 16/66] Apply suggestions from code review Fixed some typos Co-authored-by: Matt Walsh <69258106+mattwalsh-unity@users.noreply.github.com> --- .../Helpers/BuildMultiprocessTestPlayer.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 3e9d4d82d5..f7125cbc25 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -53,7 +53,7 @@ public static void DeleteBuild() } else { - Debug.Log($"exe {exePath} doesn't exists"); + Debug.Log($"exe {exePath} doesn't exist"); } break; case RuntimePlatform.OSXPlayer: @@ -65,7 +65,7 @@ public static void DeleteBuild() } else { - Debug.Log($"directory {toDelete} doesn't exists"); + Debug.Log($"directory {toDelete} doesn't exist"); } break; default: From 55e6853dcc81ed82ef666f818554521bd1090305 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 18:55:36 -0400 Subject: [PATCH 17/66] Applying suggestions --- .../Helpers/BuildMultiprocessTestPlayer.cs | 6 ++---- .../Helpers/MultiprocessOrchestration.cs | 5 ++++- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 94bc5a7dcf..88918f9950 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -10,7 +10,7 @@ /// 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 class BuildMultiprocessTestPlayer : MonoBehaviour +public static class BuildMultiprocessTestPlayer { public const string BuildAndExecuteMenuName = k_MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; private const string k_MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; @@ -80,9 +80,7 @@ public static void DeleteBuild() public static bool Build(bool isDebug = false) { // Save standalone build path to file so we can read it from standalone tests (that are not running from editor) - var f = File.CreateText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.BuildInfoFileName)); - f.Write(s_BuildPath); - f.Close(); + File.WriteAllText(Path.Combine(Application.streamingAssetsPath, MultiprocessOrchestration.BuildInfoFileName), s_BuildPath); // deleting so we don't end up testing on outdated builds if there's a build failure DeleteBuild(); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 7977f9db57..31487b675b 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -16,6 +16,8 @@ public static void StartWorkerNode() var workerNode = 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 { @@ -36,7 +38,8 @@ public static void StartWorkerNode() } catch (FileNotFoundException) { - throw new Exception($"Couldn't find build info file. {buildInstructions}"); + Debug.LogError($"Couldn't find build info file. {buildInstructions}"); + throw; } workerNode.StartInfo.UseShellExecute = false; From 17c7e7d9247ff435e7a50fe9fcf140089ffcfa36 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:02:28 -0400 Subject: [PATCH 18/66] naming --- .../Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 92078a3c98..9b6a8de724 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -43,7 +43,7 @@ public class TestCoordinator : NetworkBehaviour private Dictionary m_ClientIsFinished = new Dictionary(); public static Dictionary>.KeyCollection AllClientIdsWithResults => Instance.m_TestResultsLocal.Keys; - public static List AllClientIdExceptMine => NetworkManager.Singleton.ConnectedClients.Keys.ToList().FindAll(client => client != NetworkManager.Singleton.LocalClientId); + public static List AllClientIdsExceptMine => NetworkManager.Singleton.ConnectedClients.Keys.ToList().FindAll(client => client != NetworkManager.Singleton.LocalClientId); private void Awake() { @@ -241,13 +241,13 @@ public void ClientFinishedServerRpc(ServerRpcParams p = default) public void InvokeFromMethodActionRpc(Action methodInfo, params byte[] args) { var methodInfoString = GetMethodInfo(methodInfo); - InvokeFromMethodNameClientRpc(methodInfoString, args, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = AllClientIdExceptMine.ToArray() } }); + InvokeFromMethodNameClientRpc(methodInfoString, args, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = AllClientIdsExceptMine.ToArray() } }); } public void InvokeFromMethodActionRpc(Action methodInfo) { var methodInfoString = GetMethodInfo(methodInfo); - InvokeFromMethodNameClientRpc(methodInfoString, null, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = AllClientIdExceptMine.ToArray() } }); + InvokeFromMethodNameClientRpc(methodInfoString, null, new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = AllClientIdsExceptMine.ToArray() } }); } [ClientRpc] From 72217122102a728f54f3cafc6e24c29faf169d99 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:04:41 -0400 Subject: [PATCH 19/66] should be kept public for following PR --- .../Helpers/BuildMultiprocessTestPlayer.cs | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 4a4023870f..0c017f18d7 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -12,13 +12,13 @@ /// public static class BuildMultiprocessTestPlayer { - public const string BuildAndExecuteMenuName = k_MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; - private const string k_MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; - private const string k_MainSceneName = "MultiprocessTestingScene"; - private static string s_BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); + public const string MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; + public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; + public const string MainSceneName = "MultiprocessTestingScene"; + public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); #if UNITY_EDITOR - [MenuItem(k_MultiprocessBaseMenuName+"/Build Test Player #t")] + [MenuItem(MultiprocessBaseMenuName+"/Build Test Player #t")] public static void BuildNoDebug() { var success = Build(); @@ -28,7 +28,7 @@ public static void BuildNoDebug() } } - [MenuItem(k_MultiprocessBaseMenuName+"/Build Test Player in debug mode")] + [MenuItem(MultiprocessBaseMenuName+"/Build Test Player in debug mode")] public static void BuildDebug() { var success = Build(true); @@ -38,14 +38,14 @@ public static void BuildDebug() } } - [MenuItem(k_MultiprocessBaseMenuName+"/Delete Test Build")] + [MenuItem(MultiprocessBaseMenuName+"/Delete Test Build")] public static void DeleteBuild() { switch (Application.platform) { case RuntimePlatform.WindowsPlayer: case RuntimePlatform.WindowsEditor: - var exePath = $"{s_BuildPath}.exe"; + var exePath = $"{BuildPath}.exe"; if (File.Exists(exePath)) { File.Delete(exePath); @@ -57,7 +57,7 @@ public static void DeleteBuild() break; case RuntimePlatform.OSXPlayer: case RuntimePlatform.OSXEditor: - var toDelete = s_BuildPath + ".app"; + var toDelete = BuildPath + ".app"; if (Directory.Exists(toDelete)) { Directory.Delete(toDelete, recursive: true); @@ -80,7 +80,7 @@ public static void DeleteBuild() public static bool Build(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), s_BuildPath); + 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(); @@ -95,7 +95,7 @@ public static bool Build(bool isDebug = false) // will have more connection permission popups when launching though } - var buildPathToUse = s_BuildPath; + var buildPathToUse = BuildPath; if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor) { buildPathToUse += ".exe"; @@ -103,7 +103,7 @@ public static bool Build(bool isDebug = false) buildOptions &= ~BuildOptions.AutoRunPlayer; var buildReport = BuildPipeline.BuildPlayer( - new[] { $"Assets/Scenes/{k_MainSceneName}.unity" }, + new[] { $"Assets/Scenes/{MainSceneName}.unity" }, buildPathToUse, EditorUserBuildSettings.activeBuildTarget, buildOptions); From 4ff120694d7b6a84e363d156ac0aff4aad5da13f Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:05:51 -0400 Subject: [PATCH 20/66] apply rename --- .../Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs | 2 +- .../Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs index d49d2f857d..501920e9a8 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -31,7 +31,7 @@ public virtual void SetupTestSuite() Assert.Ignore("Ignoring tests that shouldn't run from unity editor. Performance tests should be run from remote test execution on device (this can be ran using the \"run selected tests (your platform)\" button"); } - SceneManager.LoadScene(BuildMultiprocessTestPlayer.mainSceneName, LoadSceneMode.Single); + SceneManager.LoadScene(BuildMultiprocessTestPlayer.MainSceneName, LoadSceneMode.Single); SceneManager.sceneLoaded += OnSceneLoaded; for (int i = 0; i < NbWorkers; i++) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 9b6a8de724..221e97f2a6 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -59,7 +59,7 @@ private void Awake() public void Start() { - bool isClient = Environment.GetCommandLineArgs().Any(value => value == MultiprocessOrchestration.isWorkerArg); + bool isClient = Environment.GetCommandLineArgs().Any(value => value == MultiprocessOrchestration.IsWorkerArg); if (isClient) { Debug.Log("starting MLAPI client"); From 5888f18853774000a5e397c199350bd22275b6f0 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:07:41 -0400 Subject: [PATCH 21/66] rename --- .../Runtime/MultiprocessRuntime/ExecuteStepInContext.cs | 4 ++-- .../MultiprocessRuntime/ExecuteStepInContextTests.cs | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs index 6df0b129ce..ec3f198045 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -211,9 +211,9 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s TestCoordinator.Instance.TriggerActionIDClientRpc(currentActionID, paramToPass, clientRpcParams: new ClientRpcParams { - Send = new ClientRpcSendParams { TargetClientIds = TestCoordinator.AllClientIdExceptMine.ToArray() } + Send = new ClientRpcSendParams { TargetClientIds = TestCoordinator.AllClientIdsExceptMine.ToArray() } }); - foreach (var clientId in TestCoordinator.AllClientIdExceptMine) + foreach (var clientId in TestCoordinator.AllClientIdsExceptMine) { m_ClientIsFinishedChecks.Add(TestCoordinator.ConsumeClientIsFinished(clientId)); } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs index 9ddaf1d0e5..e63c088c73 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs @@ -129,7 +129,7 @@ void Update(float __) int nbFinished = 0; for (int i = 0; i < m_NbWorkersToTest; i++) { - if (TestCoordinator.PeekLatestResult(TestCoordinator.AllClientIdExceptMine[i]) == maxValue) + if (TestCoordinator.PeekLatestResult(TestCoordinator.AllClientIdsExceptMine[i]) == maxValue) { nbFinished++; } @@ -138,8 +138,8 @@ void Update(float __) }); yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => { - Assert.That(TestCoordinator.AllClientIdExceptMine.Count, Is.EqualTo(m_NbWorkersToTest)); - foreach (var clientID in TestCoordinator.AllClientIdExceptMine) + Assert.That(TestCoordinator.AllClientIdsExceptMine.Count, Is.EqualTo(m_NbWorkersToTest)); + foreach (var clientID in TestCoordinator.AllClientIdsExceptMine) { var current = 0; foreach (var res in TestCoordinator.ConsumeCurrentResult(clientID)) From 62a1d58583fca9d941c69f15746152bfa37b68f8 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:48:38 -0400 Subject: [PATCH 22/66] using latest test framework --- testproject/Packages/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 04699ec7a3..824664ed79 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -7,7 +7,7 @@ "com.unity.multiplayer.mlapi": "file:../../com.unity.multiplayer.mlapi", "com.unity.multiplayer.transport.utp": "file:../../com.unity.multiplayer.transport.utp", "com.unity.package-validation-suite": "0.19.2-preview", - "com.unity.test-framework": "1.1.26", + "com.unity.test-framework": "1.1.27", "com.unity.test-framework.performance": "2.3.1-preview", "com.unity.textmeshpro": "3.0.6", "com.unity.timeline": "1.5.4", From eeae93cc098e6c4c9ebbcf1191d1edf94f9aa774 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:50:27 -0400 Subject: [PATCH 23/66] using proper list --- .../Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 221e97f2a6..ae5bcb8518 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -42,7 +42,7 @@ public class TestCoordinator : NetworkBehaviour private Dictionary> m_TestResultsLocal = new Dictionary>(); // this isn't super efficient, but since it's used for signaling around the tests, shouldn't be too bad private Dictionary m_ClientIsFinished = new Dictionary(); - public static Dictionary>.KeyCollection AllClientIdsWithResults => Instance.m_TestResultsLocal.Keys; + public static List AllClientIdsWithResults => Instance.m_TestResultsLocal.Keys.ToList(); public static List AllClientIdsExceptMine => NetworkManager.Singleton.ConnectedClients.Keys.ToList().FindAll(client => client != NetworkManager.Singleton.LocalClientId); private void Awake() From 520c20d449fef6fd85b8b7ced6f458755a564664 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 19:55:01 -0400 Subject: [PATCH 24/66] better exception --- .../Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index ae5bcb8518..7cf323fb73 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -282,7 +282,7 @@ public void InvokeFromMethodNameClientRpc(string methodInfoString, byte[] args, var foundMethod = foundType.GetMethod(staticMethodToExecute, BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Static); if (foundMethod == null) { - throw new Exception($"couldn't find method {staticMethodToExecute}"); + throw new MissingMethodException($"couldn't find method {staticMethodToExecute}"); } foundMethod.Invoke(null, args != null ? new object[] { args } : null); From 988574771256188509978db2ab51b6e01d23a415 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 21:10:32 -0400 Subject: [PATCH 25/66] fix for unused method better comments adding tests to make sure things don't break --- .../ExecuteStepInContext.cs | 12 ++++---- .../ExecuteStepInContextTests.cs | 30 +++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs index ec3f198045..fedba34887 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -66,20 +66,20 @@ public IEnumerator AfterTest(ITest test) /// /// This MUST be called at the beginning of each test in order to use context based steps. - /// Assumes this is called from same callsite as ExecuteInContext (and assumes this is called from IEnumerator). + /// Assumes this is called from same callsite as ExecuteStepInContext (and assumes this is called from IEnumerator, the method full name is unique + /// even with the same method name and different parameters). /// This relies on the name to be unique for each generated IEnumerator state machines /// public static void InitContextSteps() { var callerMethod = new StackFrame(1).GetMethod(); - var methodIdentifier = GetMethodIdentifier(callerMethod.DeclaringType.FullName); // since this is called from IEnumerator, this should be a generated class + var methodIdentifier = GetMethodIdentifier(callerMethod); // since this is called from IEnumerator, this should be a generated class, making it unique s_MethodIDCounter[methodIdentifier] = 0; } - private static string GetMethodIdentifier(string callerTypeName) + private static string GetMethodIdentifier(MethodBase callerMethod) { - var info = callerTypeName; - return info; + return callerMethod.DeclaringType.FullName; } internal static void InitializeAllSteps() @@ -184,7 +184,7 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s var callerMethod = new StackFrame(1).GetMethod(); // one skip frame for current method - var methodId = GetMethodIdentifier(callerMethod.DeclaringType.FullName); // assumes called from IEnumerator MoveNext, which should be the case since we're a CustomYieldInstruction + var methodId = GetMethodIdentifier(callerMethod); // assumes called from IEnumerator MoveNext, which should be the case since we're a CustomYieldInstruction. This will return a generated class name which should be unique if (!s_MethodIDCounter.ContainsKey(methodId)) { s_MethodIDCounter[methodId] = 0; diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs index e63c088c73..be6d869e75 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs @@ -23,6 +23,36 @@ public ExecuteStepInContextTests(int nbWorkersToTest) protected override int NbWorkers => m_NbWorkersToTest; protected override bool m_IsPerformanceTest => false; + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator TestWithSameName([Values(1)]int a) + { + // ExecuteStepInContext bases itself on method name to identify steps. We need to make sure that methods with + // same names, but different signatures behave correctly + InitContextSteps(); + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Assert.That(a, Is.EqualTo(1)); + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + Assert.That(BitConverter.ToInt32(bytes, 0), Is.EqualTo(1)); + }, paramToPass: BitConverter.GetBytes(a)); + } + + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator TestWithSameName([Values(2)]int a, [Values(3)]int b) + { + InitContextSteps(); + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Assert.That(b, Is.EqualTo(3)); + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + Assert.That(BitConverter.ToInt32(bytes, 0), Is.EqualTo(3)); + }, paramToPass: BitConverter.GetBytes(b)); + } + [UnityTest, MultiprocessContextBasedTest] public IEnumerator TestWithParameters([Values(1, 2, 3)] int a) { From 240e4e07927b98281c2698673989fecc73e0b3dc Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 22:21:22 -0400 Subject: [PATCH 26/66] correct spacing --- .../NetworkVariablePerformanceTests.cs | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index 30481a6613..3a9fb38058 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -60,10 +60,10 @@ private void OnSceneLoadedInitSetupSuite(Scene scene, LoadSceneMode loadSceneMod private void InitPrefab() { if (m_PrefabToSpawn == null) - { - var prefabCopy = Object.Instantiate(PrefabReference.Instance.referencedPrefab); - m_PrefabToSpawn = prefabCopy.AddComponent(); - } + { + var prefabCopy = Object.Instantiate(PrefabReference.Instance.referencedPrefab); + m_PrefabToSpawn = prefabCopy.AddComponent(); + } } [UnityTest, Performance, MultiprocessContextBasedTest] @@ -106,6 +106,7 @@ void Update(float deltaTime) } } } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; }, paramToPass: BitConverter.GetBytes(nbObjects)); @@ -176,8 +177,8 @@ void Update(float deltaTime) yield return new ExecuteStepInContext(StepExecutionContext.Clients, nbObjectsBytes => { var nbObjectsParam = BitConverter.ToInt32(nbObjectsBytes, 0); - Assert.That(GameObject.FindObjectsOfType(typeof(OneNetVar)).Length, Is.EqualTo(nbObjectsParam+1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn - }, paramToPass:BitConverter.GetBytes(nbObjects)); + Assert.That(GameObject.FindObjectsOfType(typeof(OneNetVar)).Length, Is.EqualTo(nbObjectsParam + 1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn + }, paramToPass: BitConverter.GetBytes(nbObjects)); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { Debug.Log($"finished with test for {nbObjects} expected objects and got {serverLastResult} objects"); @@ -198,6 +199,7 @@ public IEnumerator UnityTeardown() s_ServerObjectPool.Release(spawnedObject); StopSpawnedObject(spawnedObject); } + m_ServerSpawnedObjects.Clear(); }); @@ -213,8 +215,9 @@ void UpdateWaitForAllOneNetVarToDespawn(float deltaTime) TestCoordinator.Instance.ClientFinishedServerRpc(); } } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateWaitForAllOneNetVarToDespawn; - }, waitMultipleUpdates: true, ignoreTimeoutException:true); // ignoring timeout since you don't want to hide any issues in the main tests + }, waitMultipleUpdates: true, ignoreTimeoutException: true); // ignoring timeout since you don't want to hide any issues in the main tests yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => { From b93ef110498772bffc34056a8e015da018dd82ee Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 22:23:55 -0400 Subject: [PATCH 27/66] removing comment and adding something a bit more dynamic --- .../Helpers/BuildMultiprocessTestPlayer.cs | 197 ++++++++++-------- .../Helpers/MultiprocessOrchestration.cs | 9 +- .../NetworkVariablePerformanceTests.cs | 11 +- 3 files changed, 126 insertions(+), 91 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 0c017f18d7..837e6c4f87 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -6,109 +6,140 @@ #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 +namespace MLAPI.MultiprocessRuntimeTests { - public const string MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; - public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; - public const string MainSceneName = "MultiprocessTestingScene"; - public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); + /// + /// 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 - Execute multiprocess tests #%t"; + public const string MainSceneName = "MultiprocessTestingScene"; + + public const string BuildInfoFileName = "buildInfo.txt"; + + public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); + #if UNITY_EDITOR - [MenuItem(MultiprocessBaseMenuName+"/Build Test Player #t")] - public static void BuildNoDebug() - { - var success = Build(); - if (!success) + [MenuItem(MultiprocessBaseMenuName + "/Build Test Player #t")] + public static void BuildNoDebug() { - throw new Exception("Build failed!"); + var success = Build(); + if (!success) + { + throw new Exception("Build failed!"); + } } - } - [MenuItem(MultiprocessBaseMenuName+"/Build Test Player in debug mode")] - public static void BuildDebug() - { - var success = Build(true); - if (!success) + [MenuItem(MultiprocessBaseMenuName + "/Build Test Player in debug mode")] + public static void BuildDebug() { - throw new Exception("Build failed!"); + var success = Build(true); + if (!success) + { + throw new Exception("Build failed!"); + } } - } - [MenuItem(MultiprocessBaseMenuName+"/Delete Test Build")] - public static void DeleteBuild() - { - switch (Application.platform) + [MenuItem(MultiprocessBaseMenuName + "/Delete Test Build")] + public static void DeleteBuild() { - case RuntimePlatform.WindowsPlayer: - case RuntimePlatform.WindowsEditor: - var exePath = $"{BuildPath}.exe"; - if (File.Exists(exePath)) - { - File.Delete(exePath); - } - else - { - Debug.Log($"exe {exePath} doesn't exist"); - } - break; - case RuntimePlatform.OSXPlayer: - case RuntimePlatform.OSXEditor: - var toDelete = BuildPath + ".app"; - if (Directory.Exists(toDelete)) - { - Directory.Delete(toDelete, recursive: true); - } - else - { - Debug.Log($"directory {toDelete} doesn't exist"); - } - break; - default: - throw new NotImplementedException(); + switch (Application.platform) + { + case RuntimePlatform.WindowsPlayer: + case RuntimePlatform.WindowsEditor: + var exePath = $"{BuildPath}.exe"; + if (File.Exists(exePath)) + { + File.Delete(exePath); + } + else + { + Debug.Log($"exe {exePath} doesn't exist"); + } + + break; + case RuntimePlatform.OSXPlayer: + case RuntimePlatform.OSXEditor: + var toDelete = BuildPath + ".app"; + if (Directory.Exists(toDelete)) + { + Directory.Delete(toDelete, recursive: true); + } + else + { + Debug.Log($"directory {toDelete} doesn't exist"); + } + + break; + default: + throw new NotImplementedException(); + } } - } - /// - /// 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 - /// - /// - public static bool Build(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) + /// + /// 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 + /// + /// + public static bool Build(bool isDebug = false) { - 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 + // Save standalone build path to file so we can read it from standalone tests (that are not running from editor) + SaveBuildInfo(new BuildInfo() { buildPath = BuildPath, isDebug = isDebug }); + + // 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"; + } + + buildOptions &= ~BuildOptions.AutoRunPlayer; + var buildReport = BuildPipeline.BuildPlayer( + new[] { $"Assets/Scenes/{MainSceneName}.unity" }, + buildPathToUse, + EditorUserBuildSettings.activeBuildTarget, + buildOptions); + + return buildReport.summary.result == BuildResult.Succeeded; } +#endif - var buildPathToUse = BuildPath; - if (Application.platform == RuntimePlatform.WindowsPlayer || Application.platform == RuntimePlatform.WindowsEditor) + [Serializable] + public struct BuildInfo { - buildPathToUse += ".exe"; + public string buildPath; + public bool isDebug; } - buildOptions &= ~BuildOptions.AutoRunPlayer; - var buildReport = BuildPipeline.BuildPlayer( - new[] { $"Assets/Scenes/{MainSceneName}.unity" }, - buildPathToUse, - EditorUserBuildSettings.activeBuildTarget, - buildOptions); + public static BuildInfo ReadBuildInfo() + { + var jsonString = File.ReadAllText(Path.Combine(Application.streamingAssetsPath, BuildInfoFileName)); + return JsonUtility.FromJson(jsonString); + } - return buildReport.summary.result == BuildResult.Succeeded; + public static void SaveBuildInfo(BuildInfo toSave) + { + var buildInfoJson = JsonUtility.ToJson(toSave); + File.WriteAllText(Path.Combine(Application.streamingAssetsPath, BuildInfoFileName), buildInfoJson); + } } -#endif } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 31487b675b..129a29ab24 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -2,13 +2,13 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; +using MLAPI.MultiprocessRuntimeTests; using UnityEditor; using UnityEngine; using Debug = UnityEngine.Debug; public class MultiprocessOrchestration { - public const string BuildInfoFileName = "buildInfo.txt"; public const string IsWorkerArg = "-isWorker"; public static void StartWorkerNode() @@ -21,16 +21,17 @@ public static void StartWorkerNode() 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)); + + var buildPath = BuildMultiprocessTestPlayer.ReadBuildInfo().buildPath; switch (Application.platform) { case RuntimePlatform.OSXPlayer: case RuntimePlatform.OSXEditor: - workerNode.StartInfo.FileName = $"{buildInfo}.app/Contents/MacOS/testproject"; + workerNode.StartInfo.FileName = $"{buildPath}.app/Contents/MacOS/testproject"; break; case RuntimePlatform.WindowsPlayer: case RuntimePlatform.WindowsEditor: - workerNode.StartInfo.FileName = $"{buildInfo}.exe"; + workerNode.StartInfo.FileName = $"{buildPath}.exe"; break; default: throw new NotImplementedException("StartWorkerNode: Current platform not supported"); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index 3a9fb38058..63b140e36a 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -71,10 +71,13 @@ public IEnumerator TestSpawningManyObjects([Values(1, 2, 1000, 2000, 10000)] int { InitContextSteps(); - // if (!TestCoordinator.Instance.isRegistering) - // { - // yield return new WaitForSeconds(20); // uncomment to be able to attach debugger and profiler to running build - // } + if (!isRegistering && TestCoordinator.Instance.NetworkManager.IsServer && BuildMultiprocessTestPlayer.ReadBuildInfo().isDebug) + { + // build test player in debug mode to enable this + var timeToWait = 20; + Debug.Log($"Debug mode tests enabled, waiting {timeToWait} seconds to give some time to attach debugger"); + yield return new WaitForSeconds(timeToWait); + } yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => { From bc572b34f00bd947559539cf8453b6fe5a7bf137 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 5 Jul 2021 22:44:48 -0400 Subject: [PATCH 28/66] adding more info --- .../Runtime/MultiprocessRuntime/readme.md | 45 ++++++++++++++----- 1 file changed, 33 insertions(+), 12 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 946c026e82..2234806f7f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -1,21 +1,29 @@ # Multiprocess testing ## Why -Multiprocess testing can be used for different use cases like integration tests (MLAPI + actual transport for example) or performance testing. Anything requiring a more realistic environment for testing. +Multiprocess testing can be used for different use cases like +- integration tests (MLAPI + actual transport for example) +- performance testing. +- Anything requiring a more realistic environment for testing that involves having a full client and server, communicating on a real network interface using real transports in separate Unity processes. -TODO use my doc -## How it's done -### Multiple processes orchestration -todo -### CI -todo -### Client-server test coordination -todo -### Context based step execution -todo +## How to write a multiprocess test +There's a few steps to write a multiprocess test + +1. Your test class needs to inherit from `BaseMultiprocessTests` +2. Each test method needs the `MultiprocessContextBasedTest` attribute +3. Each test method needs to run `InitContextSteps();` +4. Each context based step needs to use +```C# +yield return new ExecuteStepInContext(StepExecutionContext.Clients, stepToExecute: nbObjectsBytes => { + // Something here +}); +``` + +If you want to pass in dynamic test parameters (for example nunit `Values`), you need to pass them as a `byte[]` parameter to your step, since remote execution won't have context capture from the test execution and you won't see the test's parameters. -## How to use it + +## How to run a test locally Test players need to be built first to test locally. Integration with CI should do this automatically. ![](readme-ressources/Building-Player.png) @@ -26,6 +34,19 @@ Performance tests should only be run from external processes (not from editor). ![](readme-ressources/Multiprocess.png) +## How it's done +### Multiple processes orchestration +todo +#### Local orchestration +#### Bokken orchestration +### CI +todo +#### Performance report dashboards +todo +### Client-server test coordination +todo +### Context based step execution +todo # Future considerations - Integrate with local MultiInstance tests? From f6309e9cf78b1c962bdc6925d1f5cb0bb7fcb7e7 Mon Sep 17 00:00:00 2001 From: "M. Fatih MAR" Date: Tue, 6 Jul 2021 15:08:22 +0100 Subject: [PATCH 29/66] fix/cleanup asmdefs again --- ...nity.multiplayer.mlapi.runtimetests.asmdef | 6 ++++-- .../Runtime/testproject.runtimetests.asmdef | 20 +++++-------------- 2 files changed, 9 insertions(+), 17 deletions(-) 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/Tests/Runtime/testproject.runtimetests.asmdef b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef index 45c640863c..abe114e619 100644 --- a/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef +++ b/testproject/Assets/Tests/Runtime/testproject.runtimetests.asmdef @@ -1,25 +1,15 @@ { "name": "TestProject.RuntimeTests", - "rootNamespace": "", "references": [ - "Unity.Multiplayer.MLAPI.Runtime", - "Unity.Multiplayer.MLAPI.RuntimeTests", "TestProject.ManualTests", - "UnityEngine.TestRunner", - "UnityEditor.TestRunner" + "Unity.Multiplayer.MLAPI.Runtime", + "Unity.Multiplayer.MLAPI.RuntimeTests" ], - "includePlatforms": [], - "excludePlatforms": [], - "allowUnsafeCode": false, - "overrideReferences": true, - "precompiledReferences": [ - "nunit.framework.dll" + "optionalUnityReferences": [ + "TestAssemblies" ], - "autoReferenced": false, "defineConstraints": [ "UNITY_INCLUDE_TESTS", "UNITY_EDITOR" - ], - "versionDefines": [], - "noEngineReferences": false + ] } \ No newline at end of file From cd809ec13ee5a3566d010b8b5963400465f589f5 Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Tue, 6 Jul 2021 14:07:36 -0400 Subject: [PATCH 30/66] Apply suggestions from code review Co-authored-by: M. Fatih MAR --- .../MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 31487b675b..c5eb6f9893 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -33,7 +33,7 @@ public static void StartWorkerNode() workerNode.StartInfo.FileName = $"{buildInfo}.exe"; break; default: - throw new NotImplementedException("StartWorkerNode: Current platform not supported"); + throw new NotImplementedException($"{nameof(StartWorkerNode)}: Current platform is not supported"); } } catch (FileNotFoundException) @@ -52,7 +52,7 @@ public static void StartWorkerNode() var newProcessStarted = workerNode.Start(); if (!newProcessStarted) { - throw new Exception("Process not started!"); + throw new Exception("Failed to start process!"); } } catch (Win32Exception e) From 29e05bb500a4a1737a3ad6c6fac08d1cb462a6b0 Mon Sep 17 00:00:00 2001 From: "M. Fatih MAR" Date: Thu, 8 Jul 2021 10:06:34 +0100 Subject: [PATCH 31/66] no longer ignore '[Ss]treamingAssets/buildInfo.txt' --- testproject/.gitignore | 3 --- testproject/Assets/Scenes.meta | 8 ++++++++ testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta | 8 ++++++++ .../Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta | 8 ++++++++ 4 files changed, 24 insertions(+), 3 deletions(-) create mode 100644 testproject/Assets/Scenes.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime.meta create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers.meta diff --git a/testproject/.gitignore b/testproject/.gitignore index aee9c94cdb..acbbe841e6 100644 --- a/testproject/.gitignore +++ b/testproject/.gitignore @@ -70,7 +70,4 @@ crashlytics-build.properties /[Aa]ssets/[Ss]treamingAssets/aa.meta /[Aa]ssets/[Ss]treamingAssets/aa/* -/[Aa]ssets/[Ss]treamingAssets/buildInfo.txt -/[Aa]ssets/[Ss]treamingAssets/buildInfo.txt.meta - InitTestScene* 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/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: From ed2519c1f0ff8966ba23c8d880f5e0352f5f5d98 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 10:16:10 -0400 Subject: [PATCH 32/66] PR suggestions --- .../Helpers/BuildMultiprocessTestPlayer.cs | 22 +++++++++---------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 0c017f18d7..69d0323f3f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -13,28 +13,28 @@ public static class BuildMultiprocessTestPlayer { public const string MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; - public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; + public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build Test Player #t"; public const string MainSceneName = "MultiprocessTestingScene"; public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); #if UNITY_EDITOR - [MenuItem(MultiprocessBaseMenuName+"/Build Test Player #t")] + [MenuItem(BuildAndExecuteMenuName)] public static void BuildNoDebug() { - var success = Build(); - if (!success) + var report = BuildPlayer(); + if (report.summary.result != BuildResult.Succeeded) { - throw new Exception("Build failed!"); + throw new Exception($"Build failed! {report.summary.totalErrors} errors, {report.summary}"); } } - [MenuItem(MultiprocessBaseMenuName+"/Build Test Player in debug mode")] + [MenuItem(MultiprocessBaseMenuName + "/Build Test Player in debug mode")] public static void BuildDebug() { - var success = Build(true); - if (!success) + var report = BuildPlayer(true); + if (report.summary.result != BuildResult.Succeeded) { - throw new Exception("Build failed!"); + throw new Exception($"Build failed! {report.summary.totalErrors} errors, {report.summary}"); } } @@ -77,7 +77,7 @@ public static void DeleteBuild() /// reporting. We only want to main node to do that, worker nodes should be dumb /// /// - public static bool Build(bool isDebug = false) + public 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); @@ -108,7 +108,7 @@ public static bool Build(bool isDebug = false) EditorUserBuildSettings.activeBuildTarget, buildOptions); - return buildReport.summary.result == BuildResult.Succeeded; + return buildReport; } #endif } From b08561a2cef3d8c01f1f2744f42ef528ba72c5fd Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Thu, 8 Jul 2021 11:00:24 -0400 Subject: [PATCH 33/66] changing root menu Co-authored-by: M. Fatih MAR --- .../MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 0c017f18d7..34e680c80d 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -12,7 +12,7 @@ /// public static class BuildMultiprocessTestPlayer { - public const string MultiprocessBaseMenuName = "MLAPI Multiprocess Test"; + public const string MultiprocessBaseMenuName = "MLAPI/Multiprocess Test"; public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; public const string MainSceneName = "MultiprocessTestingScene"; public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); From 1c9fb6cd007051e935b5fa0c487f06840246e489 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 12:01:18 -0400 Subject: [PATCH 34/66] # --- .../MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 69d0323f3f..fad39655a7 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -77,7 +77,7 @@ public static void DeleteBuild() /// reporting. We only want to main node to do that, worker nodes should be dumb /// /// - public static BuildReport BuildPlayer(bool isDebug = false) + 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); From 3063172adb8a0b621ca272eee3c40f4f01a83030 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 12:57:22 -0400 Subject: [PATCH 35/66] # --- testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md | 1 + 1 file changed, 1 insertion(+) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 2234806f7f..5cd2b61ac1 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -37,6 +37,7 @@ Performance tests should only be run from external processes (not from editor). ## How it's done ### Multiple processes orchestration todo +TODO add diagrams for clients vs server and for automation bokken plans #### Local orchestration #### Bokken orchestration ### CI From 45071ce585399eaaa46bcb1d3caa9657e7566e5c Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 12:58:13 -0400 Subject: [PATCH 36/66] sample code --- .../Runtime/MultiprocessRuntime/readme.md | 130 +++++++++++++++++- 1 file changed, 129 insertions(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 5cd2b61ac1..c0cd218f69 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -51,4 +51,132 @@ todo # Future considerations - Integrate with local MultiInstance tests? -- Have ExecuteStepInContext a game facing feature for sequencing client-server actions? \ No newline at end of file +- Have ExecuteStepInContext a game facing feature for sequencing client-server actions? + + +# Sample +```cs +using System; +using System.Collections; +using NUnit.Framework; +using Unity.PerformanceTesting; +using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.TestTools; +using static ExecuteStepInContext; + +namespace MLAPI.MultiprocessRuntimeTests +{ + /* + * Multiprocess testing can be used for different use cases like + + integration tests (MLAPI + actual transport for example) + performance testing. + Anything requiring a more realistic environment for testing that involves having a full client and server, + communicating on a real network interface using real transports in separate Unity processes. + +test locally, same tests execute on bokken (just the way they are deployed will change, same code logic executes) + */ + public class DemoProcessTest : BaseMultiprocessTests + { + protected override int NbWorkers { get; } = 2; + protected override bool m_IsPerformanceTest { get; } = false; + + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator MyTest() + { + InitContextSteps(); + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Debug.Log("server stuff"); + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + Debug.Log("client stuff"); + Assert.That(1, Is.EqualTo(1)); + // throw new Exception("asdf"); // this client side exception will be communicated to the coordinator, making the test fail + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + TestCoordinator.Instance.WriteTestResultsServerRpc(123); + TestCoordinator.Instance.WriteTestResultsServerRpc(123); + TestCoordinator.Instance.WriteTestResultsServerRpc(123); // could be replaced by json string instead for ease of use? + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + TestCoordinator.ConsumeCurrentResult(); + foreach (var clientID in TestCoordinator.AllClientIdsExceptMine) + { + TestCoordinator.ConsumeCurrentResult(clientID); + } + + foreach (var (clientID, result) in TestCoordinator.ConsumeCurrentResult()) + { + Assert.That(result, Is.EqualTo(123)); + } + }); + + + int someValue = 456; // one caveat to executeStepInContext is contrary to instinct, this is not shared between server and client execution. + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + var valueComingFromServer = BitConverter.ToInt32(bytes, 0); + }, paramToPass: BitConverter.GetBytes(456)); // could be replaced by JSON string instead for ease of use? + // useful for taking in [Values] method parameters as these are only known by the server + + + // when you have client steps that take more than one frame + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + void Update(float _) + { + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + TestCoordinator.Instance.ClientFinishedServerRpc(); // since finishOnInvoke is false, we need to do this manually + } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + }, waitMultipleUpdates: true); // this keeps waiting "are you done? are you done? are you done?" + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + int cpt = 0; + void Update(float _) + { + TestCoordinator.Instance.WriteTestResultsServerRpc(Time.time); + } + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + }, additionalIsFinishedWaiter: () => // this keeps waiting "are you done? are you done? are you done?" + { + foreach (var (clientId, latest) in TestCoordinator.ConsumeCurrentResult()) + { + return latest >= 10; + } + return false; + }); + + + } + + [UnityTest, Performance] // already existing performance framework https://docs.unity3d.com/Packages/com.unity.test-framework.performance@2.8/manual/index.html + public IEnumerator PerfTest() + { + var totalAllocSampleGroup = new SampleGroup("GC Alloc", SampleUnit.Kilobyte); + var allocStat = Profiler.GetTotalAllocatedMemoryLong(); + Measure.Custom(totalAllocSampleGroup, allocStat / 1024); // this will record in Unity's shared Performance DB. + // Dashboards will be able to display these stats overtime + yield return null; + } + + /* + * To run these tests, go to unity menu, build + * run the test you want from test runner + * + * Can run these from separate player, so both the server and client are in builds (and not just the clients while the + * server is in the editor) good for perf tests + * + * I timed myself, I was about to create this demo test code with no debugging involved in 30 minutes. It's pretty empty, + * but it can give you an idea of the overhead involved, it's pretty short (and I had to dig for talking about every single parameters) + * + */ + } +} + +``` \ No newline at end of file From c6d3b4a61ad9eaae1faf28ad0d6e70d511b7bdf6 Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:27:36 -0400 Subject: [PATCH 37/66] rename test scene Co-authored-by: M. Fatih MAR --- .../MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 34e680c80d..0904a7485a 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -14,7 +14,7 @@ public static class BuildMultiprocessTestPlayer { public const string MultiprocessBaseMenuName = "MLAPI/Multiprocess Test"; public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build - Execute multiprocess tests #%t"; - public const string MainSceneName = "MultiprocessTestingScene"; + public const string MainSceneName = "MultiprocessTestScene"; public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); #if UNITY_EDITOR From 2aa372b5d4cbe03fb0363740cfb0f0965c0e2242 Mon Sep 17 00:00:00 2001 From: Sam Bellomo <71790295+SamuelBellomo@users.noreply.github.com> Date: Thu, 8 Jul 2021 14:30:28 -0400 Subject: [PATCH 38/66] Update testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs Co-authored-by: M. Fatih MAR --- .../Helpers/BuildMultiprocessTestPlayer.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 0904a7485a..365e0b3408 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -57,14 +57,14 @@ public static void DeleteBuild() break; case RuntimePlatform.OSXPlayer: case RuntimePlatform.OSXEditor: - var toDelete = BuildPath + ".app"; - if (Directory.Exists(toDelete)) + var appPath = BuildPath + ".app"; + if (Directory.Exists(appPath)) { - Directory.Delete(toDelete, recursive: true); + Directory.Delete(appPath, recursive: true); } else { - Debug.Log($"directory {toDelete} doesn't exist"); + Debug.Log($"[{nameof(BuildMultiprocessTestPlayer)}] MacOS build does not exist ({appPath})"); } break; default: From 484700f01e6b9f0490ddbe870ecc2cf19982e099 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 14:33:20 -0400 Subject: [PATCH 39/66] rename for test scene --- ...MultiprocessTestingScene.unity => MultiprocessTestScene.unity} | 0 ...ssTestingScene.unity.meta => MultiprocessTestScene.unity.meta} | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename testproject/Assets/Scenes/{MultiprocessTestingScene.unity => MultiprocessTestScene.unity} (100%) rename testproject/Assets/Scenes/{MultiprocessTestingScene.unity.meta => MultiprocessTestScene.unity.meta} (100%) diff --git a/testproject/Assets/Scenes/MultiprocessTestingScene.unity b/testproject/Assets/Scenes/MultiprocessTestScene.unity similarity index 100% rename from testproject/Assets/Scenes/MultiprocessTestingScene.unity rename to testproject/Assets/Scenes/MultiprocessTestScene.unity diff --git a/testproject/Assets/Scenes/MultiprocessTestingScene.unity.meta b/testproject/Assets/Scenes/MultiprocessTestScene.unity.meta similarity index 100% rename from testproject/Assets/Scenes/MultiprocessTestingScene.unity.meta rename to testproject/Assets/Scenes/MultiprocessTestScene.unity.meta From cba7b19d11daeef64ad3a48cdc479945dbe85079 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 14:37:56 -0400 Subject: [PATCH 40/66] proper rename for scene --- testproject/Assets/Scenes/MultiprocessTestScene.unity | 2 +- testproject/ProjectSettings/EditorBuildSettings.asset | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Scenes/MultiprocessTestScene.unity b/testproject/Assets/Scenes/MultiprocessTestScene.unity index 65e0b7ee89..d4a6e985ce 100644 --- a/testproject/Assets/Scenes/MultiprocessTestScene.unity +++ b/testproject/Assets/Scenes/MultiprocessTestScene.unity @@ -683,7 +683,7 @@ MonoBehaviour: ProtocolVersion: 0 NetworkTransport: {fileID: 1674777073} RegisteredScenes: - - MultiprocessTestingScene + - MultiprocessTestScene - SampleScene AllowRuntimeSceneChanges: 0 PlayerPrefab: {fileID: 4700706668509470175, guid: 7eeaaf9e50c0afc4dab93584a54fb0d6, diff --git a/testproject/ProjectSettings/EditorBuildSettings.asset b/testproject/ProjectSettings/EditorBuildSettings.asset index c8f83681c4..0aad2d2bec 100644 --- a/testproject/ProjectSettings/EditorBuildSettings.asset +++ b/testproject/ProjectSettings/EditorBuildSettings.asset @@ -45,6 +45,6 @@ EditorBuildSettings: path: Assets/Tests/Manual/NetworkAnimatorTests/NetworkAnimatorEnhancement.unity guid: f88da8bb8d07e11418eaad6524d5cc12 - enabled: 1 - path: Assets/Scenes/MultiprocessTestingScene.unity + path: Assets/Scenes/MultiprocessTestScene.unity guid: 76743cb7b342c49279327834918a9c6e m_configObjects: {} From c2abf576da8ecb9819d70b1a0cb6b062878aa017 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 14:48:13 -0400 Subject: [PATCH 41/66] update to screenshot --- .../readme-ressources/Building-Player.png | Bin 19604 -> 35626 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png index 3cce0f04d29359936253432335164b6e7675938a..d731ed8255d60a5d63f02d055da8f88a410bdbc1 100644 GIT binary patch literal 35626 zcmZVl1ymeCvo{Xo?u6hh4hasyVR5&h!JXg|oCH|hVX@$_SRlb&gS!O?E(z`sV9`Z( z|2+4-?|bk0zB#97YP#xIHC^2^r@N~r_PwSOJ`NQQ5)u-=in6>85)$(93mt`p@p2~N z9IJh)P;iu!d#@rVNB`c_{iCCc9TF0IijAcui3%tCsFjta<>(a08yrtRotT&e9ZS%7 z@2}pz-m${r-n1-J(@hGZO;o*pq!R7ER%g5f`iBPY(W=BEYVNE&&5sY$?;d5pPNZpN z9cv?fP<8smpA1JG{4s{Ez=DUD@J71DGe8A7<`1%M{3~QyblYj<`Z^&OWkhH-NViS#c2&hYT6=HLhf&9C0xnz7!C zy9WfKrxk%f_)(&wUU6YWp>7}%iCAq$U;Tx?_|?G9Q01eB2GZLX8Vd1jrQT{I%`M41E|I$cN{}GhYl~Yl9x$4?@+S$2zIkuznw?eFUG9|)3!zt{`uYUgc5@9*m3<|XDY$@pIqVlVW6 zs(Bdc|4YQ%S(4FE<2}8cyQdw!5H~M3FC!3#o}OO9)Apm7j=bW3!(Z+s86CX6J;Zo; z{QUg5{RFt(J?(k;L`6k;c=>tw`MF*sxV!?~ysiAX+`O3nkCOkZN8ZlM#?#Tm+tJ;P z{y%!HtlfRQB^eq2W9a`K|K~aF{2l+tlAG6mH|wQ=JpbYF@Nx6<{6F0

KK4ioJLA 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 M07*qoM6N<$f{k%mqyPW_ From 90fffe4ba7edbac100c605946d09aa6070210def Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 15:00:53 -0400 Subject: [PATCH 42/66] # --- .../Helpers/BuildMultiprocessTestPlayer.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 2d14b4b44e..a6c5378122 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -15,7 +15,7 @@ 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"; - public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTestBuild"); + public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTests/MultiprocessTestBuild"); #if UNITY_EDITOR [MenuItem(BuildAndExecuteMenuName)] @@ -24,7 +24,7 @@ public static void BuildNoDebug() var report = BuildPlayer(); if (report.summary.result != BuildResult.Succeeded) { - throw new Exception($"Build failed! {report.summary.totalErrors} errors, {report.summary}"); + throw new Exception($"Build failed! {report.summary.totalErrors} errors"); } } @@ -34,7 +34,7 @@ public static void BuildDebug() var report = BuildPlayer(true); if (report.summary.result != BuildResult.Succeeded) { - throw new Exception($"Build failed! {report.summary.totalErrors} errors, {report.summary}"); + throw new Exception($"Build failed! {report.summary.totalErrors} errors"); } } From 2c915e92a9ca644db6af7cfa94abade02529b576 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 15:16:56 -0400 Subject: [PATCH 43/66] simpler flow --- .../Helpers/BuildMultiprocessTestPlayer.cs | 38 +++++-------------- 1 file changed, 9 insertions(+), 29 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index a6c5378122..a9b3d7ecf5 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -15,7 +15,8 @@ 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"; - public static string BuildPath => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTests/MultiprocessTestBuild"); + public static string BuildPathDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTests"); + public static string BuildPath => Path.Combine(BuildPathDirectory, "/MultiprocessTestBuild"); #if UNITY_EDITOR [MenuItem(BuildAndExecuteMenuName)] @@ -38,37 +39,16 @@ public static void BuildDebug() } } - [MenuItem(MultiprocessBaseMenuName+"/Delete Test Build")] + [MenuItem(MultiprocessBaseMenuName + "/Delete Test Build")] public static void DeleteBuild() { - switch (Application.platform) + if (Directory.Exists(BuildPathDirectory)) { - case RuntimePlatform.WindowsPlayer: - case RuntimePlatform.WindowsEditor: - var exePath = $"{BuildPath}.exe"; - if (File.Exists(exePath)) - { - File.Delete(exePath); - } - else - { - Debug.Log($"exe {exePath} doesn't exist"); - } - break; - case RuntimePlatform.OSXPlayer: - case RuntimePlatform.OSXEditor: - var appPath = BuildPath + ".app"; - if (Directory.Exists(appPath)) - { - Directory.Delete(appPath, recursive: true); - } - else - { - Debug.Log($"[{nameof(BuildMultiprocessTestPlayer)}] MacOS build does not exist ({appPath})"); - } - break; - default: - throw new NotImplementedException(); + Directory.Delete(BuildPathDirectory, recursive: true); + } + else + { + Debug.Log($"[{nameof(BuildMultiprocessTestPlayer)}] build directory does not exist ({BuildPathDirectory}) not deleting anything"); } } From 9a3bd9b9b698955ad47384fe09d136965d2210ae Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 15:30:03 -0400 Subject: [PATCH 44/66] fixes --- .../Helpers/BuildMultiprocessTestPlayer.cs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index a9b3d7ecf5..3449a4d658 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -15,8 +15,8 @@ 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"; - public static string BuildPathDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds/MultiprocessTests"); - public static string BuildPath => Path.Combine(BuildPathDirectory, "/MultiprocessTestBuild"); + 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)] @@ -80,6 +80,7 @@ private static BuildReport BuildPlayer(bool isDebug = false) { buildPathToUse += ".exe"; } + Debug.Log($"Starting multiprocess player build using path {buildPathToUse}"); buildOptions &= ~BuildOptions.AutoRunPlayer; var buildReport = BuildPipeline.BuildPlayer( @@ -88,6 +89,7 @@ private static BuildReport BuildPlayer(bool isDebug = false) EditorUserBuildSettings.activeBuildTarget, buildOptions); + Debug.Log($"done building"); return buildReport; } #endif From 0574d736a88e714acb3852eba2fa4e45667b1976 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:38:24 -0400 Subject: [PATCH 45/66] adding more details --- .../OrchestrationOverview.jpg | Bin 0 -> 199286 bytes .../OrchestrationOverview.jpg.meta | 96 +++++++++++ .../Runtime/MultiprocessRuntime/readme.md | 161 +++++++++--------- 3 files changed, 176 insertions(+), 81 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg.meta diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg new file mode 100644 index 0000000000000000000000000000000000000000..f346dd85b7317b9d9676d0deeeb5aa8d8596cac4 GIT binary patch literal 199286 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�(tRS{%wq?oGZ0?jj$V;-0sD=99FkqBOII$SZWIk)a`w$cG6#@JB)?~G9= z_Y@6!+4ARdYZNa5$9@$LustDgH;S8!@f%})+9^)!0Up<*#tMuWPS1W54u8K&=3P{_Ik$*E4!{r%uXWYv1TkNifgQotibn~H5)>6J}SS8YtBG! zYEE%TawfB*OQ?&VUGH;)>CFYg>`#VLzRnSeLsHCsA==JXOLDZHtfAg$i;&16-m77?Ch~ ztWRJ#PF)Jc>YML+17PU2Q&@*+9*YYCdu~(s90py0gkHERu-O9HzZ_^Gk~ai3LsvAG zHA44w5b%8c*udy5jpj0U`qsIFtQf1fFpUm{Mf|gm1zw7{df~kw0$|<3y8z#*)Ba(s z9Q*EynoSFvi3dBfd+Rf~Ob4Dz?I35l732-eUrahbtSli8DQiDemYPEZ#EGj2pgJDY z&eqmv06X1S8mpF!2z-PHd|Utpx;G68RDc36JOPynwk=KYk%Y8@578u$ z4t9`73Q_OUEcDof6xSLmM>kYDe1;hM+dar*hQYe#!$zc58qe!c*q%95ALAhzsafX5 z`I2=`NPQ}MAA^=TrpxXgHG#0JgaU1N+sg+zT2%L++szw}dRxkxm*h(#J8`3UApF;j znn`(yfcPh>iQ1K>d#{T8u*|K9YbCc`WS5hI+U1D9U6K@PJov^-*B8`tl7=4tni^BR zadlIJTk-G1xP5(qE^dv41iuUfG{(Aj4z@gDt>8LwNBlug*~$e@bLWEq3T>8F*NQ zpzRI7N5iz&iOOwhIz|(ve#MsmEH06^ylAl7tt4#du7*z7&kTvj)^4@V_sM4qN@C?} z=V0^QN-`a1t?Op5R&`O9Tov51>byTe4qxX=2GT7_p&&?5PP6sRe7{2rQv{`89shNG z7p)@)59)x(1D+rw^BRLke;SuY3PwsA*J&bx_GmuXa!GD$M;KDj>?|No1Px7YAzryE zYYBLK!L|VC4)fmcqCqo4luC1L4pO3pxp6myk3H!*ZI-zO|3%5t8f1|~-hy)2WY{eR zhGs%}OCN-O*+a3$#T|~d2^woC%toNFFMz^y&vhawOhBQq2qsR1|5(&6@}2eX?&7w- z*%_o6>Ww~4O>Q@<_ij8p*&avcGMi4)ATTaB8oXs_DF2OVZzPq=d@9jbNn>dPYHx6O zLEBJi4DhWQk5E3?L*_9nd+C5i8)ivf2r=n#ekx$oF5q(^HGt=V*r8n92nf1=$U}&D>4KlA z*$ptq;4K3~CHfh*S#N$OtPW_0r#-CjG8je<;Bit0z|;jT4m@HlsP|2Tx3>@klVcaO z(tUxHd*pJf!CTe{*tS73x!tTpgf*QSakq?mo(S`{UqWhFRIdpo>?J|GpVLZU!cGXq zA!`BKZW=2L)S^H)6IO0BSSWzXfrW-MTnD5B@?h8j%|d}p-gZzmw=z1?!Ha|fjfdpB z+aayqDwHRZpUQ!-w^SssdbjDKTp1l_uZSRu5B*FAIFcDte;Vz*<^A1}odSZN?cE)N z`%3%4p-y4i&OxCwO#o^yDgF*VsJ-OqQ2M=qk>4SR`FO}`tOAF|_F(;sachh2Z2htt zjQz{#*cUr{=$GkmJZk>-&RA?b#_DK4qX`-NDFVU)qI?KAouCRj1>fe~y&f&3j=Y0X zM~Xj%RglJ44vpv+&=;Q z!tkgG*Myw=hYkOTVZU!{3Do5lL+^A3dI~lXPQfZ#nD6-t6$JyM4+e&fcy#g?|INL> zTm0iud!z|D`B!BBwxZ&q?VkLjB+QOV!v(UD4xZo}2?wbErCeW=(e!_O|I9=UUnb<3 zM`6_H!>ISQPf^wRBm7PMaDhc3{Aa&(BJv$$A)1L(Q9SifF$ awj96N@_p@V^jsVr;^-_c*5AO>y8i;?OwxP+ literal 0 HcmV?d00001 diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg.meta new file mode 100644 index 0000000000..84c6f4f98b --- /dev/null +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/OrchestrationOverview.jpg.meta @@ -0,0 +1,96 @@ +fileFormatVersion: 2 +guid: 7b6f909ad23994f9c9cb51710d1e1e07 +TextureImporter: + internalIDToNameTable: [] + externalObjects: {} + serializedVersion: 11 + mipmaps: + mipMapMode: 0 + enableMipMap: 1 + sRGBTexture: 1 + linearTexture: 0 + fadeOut: 0 + borderMipMap: 0 + mipMapsPreserveCoverage: 0 + alphaTestReferenceValue: 0.5 + mipMapFadeDistanceStart: 1 + mipMapFadeDistanceEnd: 3 + bumpmap: + convertToNormalMap: 0 + externalNormalMap: 0 + heightScale: 0.25 + normalMapFilter: 0 + isReadable: 0 + streamingMipmaps: 0 + streamingMipmapsPriority: 0 + vTOnly: 0 + grayScaleToAlpha: 0 + generateCubemap: 6 + cubemapConvolution: 0 + seamlessCubemap: 0 + textureFormat: 1 + maxTextureSize: 2048 + textureSettings: + serializedVersion: 2 + filterMode: 1 + aniso: 1 + mipBias: 0 + wrapU: 0 + wrapV: 0 + wrapW: 0 + nPOTScale: 1 + lightmap: 0 + compressionQuality: 50 + spriteMode: 0 + spriteExtrude: 1 + spriteMeshType: 1 + alignment: 0 + spritePivot: {x: 0.5, y: 0.5} + spritePixelsToUnits: 100 + spriteBorder: {x: 0, y: 0, z: 0, w: 0} + spriteGenerateFallbackPhysicsShape: 1 + alphaUsage: 1 + alphaIsTransparency: 0 + spriteTessellationDetail: -1 + textureType: 0 + textureShape: 1 + singleChannelComponent: 0 + flipbookRows: 1 + flipbookColumns: 1 + maxTextureSizeSet: 0 + compressionQualitySet: 0 + textureFormatSet: 0 + ignorePngGamma: 0 + applyGammaDecoding: 0 + platformSettings: + - serializedVersion: 3 + buildTarget: DefaultTexturePlatform + maxTextureSize: 2048 + resizeAlgorithm: 0 + textureFormat: -1 + textureCompression: 1 + compressionQuality: 50 + crunchedCompression: 0 + allowsAlphaSplitting: 0 + overridden: 0 + androidETC2FallbackOverride: 0 + forceMaximumCompressionQuality_BC6H_BC7: 0 + spriteSheet: + serializedVersion: 2 + sprites: [] + outline: [] + physicsShape: [] + bones: [] + spriteID: + internalID: 0 + vertices: [] + indices: + edges: [] + weights: [] + secondaryTextures: [] + spritePackingTag: + pSDRemoveMatte: 0 + pSDShowRemoveMatteOption: 0 + userData: + assetBundleName: + assetBundleVariant: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index c0cd218f69..932864695b 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -2,10 +2,11 @@ ## Why Multiprocess testing can be used for different use cases like -- integration tests (MLAPI + actual transport for example) +- integration tests (MLAPI + actual transport or multi-scene testing for example) - performance testing. - Anything requiring a more realistic environment for testing that involves having a full client and server, communicating on a real network interface using real transports in separate Unity processes. +The tests you write and test locally will be deployed dynamically to bokken instances. The tests shouldn't have to worry about what hardware it runs on, this should be abstracted away by "workers" and "coordinator". ## How to write a multiprocess test There's a few steps to write a multiprocess test @@ -19,113 +20,90 @@ yield return new ExecuteStepInContext(StepExecutionContext.Clients, stepToExecut // Something here }); ``` +A test method would look like +```C# + [UnityTest, MultiprocessContextBasedTest] + public IEnumerator MyTest() + { + InitContextSteps(); // the only call that should be made outside of context based tests + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => + { + Debug.Log("server stuff"); + }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + { + Debug.Log("client stuff"); + Assert.That(1, Is.EqualTo(1)); + throw new Exception("asdf"); // this client side exception will be communicated to the coordinator, making the test fail + }); + } +``` +5. Your test code shouldn't execute outside of these steps (as that test method can be executed multiple times, once for step registration and once for the actual test run for example) If you want to pass in dynamic test parameters (for example nunit `Values`), you need to pass them as a `byte[]` parameter to your step, since remote execution won't have context capture from the test execution and you won't see the test's parameters. -## How to run a test locally -Test players need to be built first to test locally. Integration with CI should do this automatically. - -![](readme-ressources/Building-Player.png) - -Then run the tests. - -Performance tests should only be run from external processes (not from editor). This way the server code will run in a build, just as much as client code. - -![](readme-ressources/Multiprocess.png) - -## How it's done -### Multiple processes orchestration -todo -TODO add diagrams for clients vs server and for automation bokken plans -#### Local orchestration -#### Bokken orchestration -### CI -todo -#### Performance report dashboards -todo -### Client-server test coordination -todo -### Context based step execution -todo - -# Future considerations -- Integrate with local MultiInstance tests? -- Have ExecuteStepInContext a game facing feature for sequencing client-server actions? - - -# Sample -```cs -using System; -using System.Collections; -using NUnit.Framework; -using Unity.PerformanceTesting; -using UnityEngine; -using UnityEngine.Profiling; -using UnityEngine.TestTools; +```C# +using //... using static ExecuteStepInContext; namespace MLAPI.MultiprocessRuntimeTests { - /* - * Multiprocess testing can be used for different use cases like - - integration tests (MLAPI + actual transport for example) - performance testing. - Anything requiring a more realistic environment for testing that involves having a full client and server, - communicating on a real network interface using real transports in separate Unity processes. - -test locally, same tests execute on bokken (just the way they are deployed will change, same code logic executes) - */ public class DemoProcessTest : BaseMultiprocessTests { - protected override int NbWorkers { get; } = 2; - protected override bool m_IsPerformanceTest { get; } = false; + protected override int NbWorkers { get; } = 2; // spawns 2 clients connecting to the test runner + protected override bool m_IsPerformanceTest { get; } = false; // specifies whether this should execute from editor or not - [UnityTest, MultiprocessContextBasedTest] + [UnityTest, MultiprocessContextBasedTest] // attribute necessary for context based step execution public IEnumerator MyTest() { - InitContextSteps(); + InitContextSteps(); // necessary to initialize context based steps + + // These steps execute sequentially. yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { Debug.Log("server stuff"); }); + // for example, the test runner will yield on the same step until clients all report they are done with this step. Once all clients report they are done, the test can continue to the same step. yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { Debug.Log("client stuff"); Assert.That(1, Is.EqualTo(1)); - // throw new Exception("asdf"); // this client side exception will be communicated to the coordinator, making the test fail + throw new Exception("asdf"); // this client side exception will be communicated to the coordinator, making the test fail }); yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { + // To write results to the test runner, call this method: TestCoordinator.Instance.WriteTestResultsServerRpc(123); TestCoordinator.Instance.WriteTestResultsServerRpc(123); TestCoordinator.Instance.WriteTestResultsServerRpc(123); // could be replaced by json string instead for ease of use? }); - yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => + yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { + // consumes first result sent above from any client TestCoordinator.ConsumeCurrentResult(); - foreach (var clientID in TestCoordinator.AllClientIdsExceptMine) - { - TestCoordinator.ConsumeCurrentResult(clientID); - } - + // consumes all results from all clients foreach (var (clientID, result) in TestCoordinator.ConsumeCurrentResult()) { Assert.That(result, Is.EqualTo(123)); } + // consumes results for individual clients + foreach (var clientID in TestCoordinator.AllClientIdsExceptMine) + { + TestCoordinator.ConsumeCurrentResult(clientID); + } }); int someValue = 456; // one caveat to executeStepInContext is contrary to instinct, this is not shared between server and client execution. + // to send that value to clients, "paramToPass" needs to be used yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { var valueComingFromServer = BitConverter.ToInt32(bytes, 0); }, paramToPass: BitConverter.GetBytes(456)); // could be replaced by JSON string instead for ease of use? // useful for taking in [Values] method parameters as these are only known by the server - - // when you have client steps that take more than one frame + // when you have client steps that take more than one frame, you can subscribe to the OnUpdate callback on CallbackComponent yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { void Update(float _) @@ -134,7 +112,7 @@ test locally, same tests execute on bokken (just the way they are deployed will TestCoordinator.Instance.ClientFinishedServerRpc(); // since finishOnInvoke is false, we need to do this manually } NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; - }, waitMultipleUpdates: true); // this keeps waiting "are you done? are you done? are you done?" + }, waitMultipleUpdates: true); // this keeps waiting "are you done? are you done? are you done?" and relies on the clients calling the "ClientFinishedServerRpc" yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { int cpt = 0; @@ -143,7 +121,7 @@ test locally, same tests execute on bokken (just the way they are deployed will TestCoordinator.Instance.WriteTestResultsServerRpc(Time.time); } NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; - }, additionalIsFinishedWaiter: () => // this keeps waiting "are you done? are you done? are you done?" + }, additionalIsFinishedWaiter: () => // this keeps waiting "are you done? are you done? are you done?" until this lambda returns true { foreach (var (clientId, latest) in TestCoordinator.ConsumeCurrentResult()) { @@ -151,8 +129,6 @@ test locally, same tests execute on bokken (just the way they are deployed will } return false; }); - - } [UnityTest, Performance] // already existing performance framework https://docs.unity3d.com/Packages/com.unity.test-framework.performance@2.8/manual/index.html @@ -164,19 +140,42 @@ test locally, same tests execute on bokken (just the way they are deployed will // Dashboards will be able to display these stats overtime yield return null; } - - /* - * To run these tests, go to unity menu, build - * run the test you want from test runner - * - * Can run these from separate player, so both the server and client are in builds (and not just the clients while the - * server is in the editor) good for perf tests - * - * I timed myself, I was about to create this demo test code with no debugging involved in 30 minutes. It's pretty empty, - * but it can give you an idea of the overhead involved, it's pretty short (and I had to dig for talking about every single parameters) - * - */ } } -``` \ No newline at end of file +``` + + +## How to run a test locally +Test players need to be built first to test locally. Integration with CI should do this automatically. + +![](readme-ressources/Building-Player.png) + +Then run the tests. + +Performance tests should only be run from external processes (not from editor). This way the server code will run in a build, just as much as client code. + +![](readme-ressources/Multiprocess.png) + +## How it's done +### Multiple processes orchestration +The test runner executes the main node's tests. The tests are in charge of launching their needed workers. +With the bokken integration, we'll need to be careful about ressource contention at Unity, these tests could be heavy on ressources. +Tests when launched locally will simply create new OS processes for each worker players. +![](readme-ressources/OrchestrationOverview.jpg) +#### Bokken orchestration +TODO bokken orchestration? +### CI +todo +#### Performance report dashboards +todo +### Client-server test coordination +A Test Coordinator is in charge of managing communication between the nodes, executing remote test code. The test coordinator is also in charge of process cleanup, if for example the server crashes, so we don't have zombie clients laying around. +The test coordinator in client mode will automatically try to connect to a server on Start(). +### Context based step execution +Test methods are executed twice. Once in "registration" mode, to have all the steps register themselves using a unique ID. This ID is deterministic between client and server processes, so that when a server calls a step during actual test execution, the clients have the same ID associated with the same lambda. +During test execution, the main node's step will call an RPC on clients to trigger their pre-registered lambda. The main node's step will then yield until it receives a "client done" RPC from all clients. The main node's test will then be able to continue execution to the next step. + +# Future considerations +- Integrate with local MultiInstance tests? +- Have ExecuteStepInContext a game facing feature for sequencing client-server actions? \ No newline at end of file From e36106952ae34c32e3f5834d74d8b23b2cd12eda Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:41:02 -0400 Subject: [PATCH 46/66] # --- testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 932864695b..c5463545c3 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -164,7 +164,7 @@ With the bokken integration, we'll need to be careful about ressource contention Tests when launched locally will simply create new OS processes for each worker players. ![](readme-ressources/OrchestrationOverview.jpg) #### Bokken orchestration -TODO bokken orchestration? +todo ### CI todo #### Performance report dashboards From ec3fbcebc3eab32da092bf157353a019f05ef5e4 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:46:25 -0400 Subject: [PATCH 47/66] # --- .../Runtime/MultiprocessRuntime/readme.md | 34 +++++++++++++++++-- 1 file changed, 31 insertions(+), 3 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index c5463545c3..975fb5fed7 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -14,7 +14,7 @@ There's a few steps to write a multiprocess test 1. Your test class needs to inherit from `BaseMultiprocessTests` 2. Each test method needs the `MultiprocessContextBasedTest` attribute 3. Each test method needs to run `InitContextSteps();` -4. Each context based step needs to use +4. Each context based step can use ```C# yield return new ExecuteStepInContext(StepExecutionContext.Clients, stepToExecute: nbObjectsBytes => { // Something here @@ -38,7 +38,33 @@ A test method would look like }); } ``` -5. Your test code shouldn't execute outside of these steps (as that test method can be executed multiple times, once for step registration and once for the actual test run for example) +Your test code shouldn't execute outside of these steps (as that test method can be executed multiple times, once for step registration and once for the actual test run for example) + +Another way to write a multiprocess test without context based steps is to use TestCoordinator directly. +```C# + private static void ExecuteSimpleCoordinatorTest() + { + TestCoordinator.Instance.WriteTestResultsServerRpc(float.PositiveInfinity); + } + + [UnityTest] + public IEnumerator CheckTestCoordinator() + { + // Call the client side method + TestCoordinator.Instance.InvokeFromMethodActionRpc(ExecuteSimpleCoordinatorTest); + + var nbResults = 0; + for (int i = 0; i < NbWorkers; i++) // wait and test for the two clients + { + yield return new WaitUntil(TestCoordinator.ResultIsSet()); + + var (clientId, result) = TestCoordinator.ConsumeCurrentResult().Take(1).Single(); + Assert.Greater(result, 0f); + nbResults++; + } + Assert.That(nbResults, Is.EqualTo(NbWorkers)); + } +``` If you want to pass in dynamic test parameters (for example nunit `Values`), you need to pass them as a `byte[]` parameter to your step, since remote execution won't have context capture from the test execution and you won't see the test's parameters. @@ -162,8 +188,10 @@ Performance tests should only be run from external processes (not from editor). The test runner executes the main node's tests. The tests are in charge of launching their needed workers. With the bokken integration, we'll need to be careful about ressource contention at Unity, these tests could be heavy on ressources. Tests when launched locally will simply create new OS processes for each worker players. + ![](readme-ressources/OrchestrationOverview.jpg) -#### Bokken orchestration +*Note that this diagram is still WIP* +### Bokken orchestration todo ### CI todo From 6a78870e5625a93f03cc3743055fa4a6186f5e24 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:51:27 -0400 Subject: [PATCH 48/66] moving to lighter jpg --- .../readme-ressources/Building-Player.jpg | Bin 0 -> 11346 bytes ...Player.png.meta => Building-Player.jpg.meta} | 2 +- .../readme-ressources/Building-Player.png | Bin 35626 -> 0 bytes .../readme-ressources/Multiprocess.jpg | Bin 0 -> 23858 bytes ...tiprocess.png.meta => Multiprocess.jpg.meta} | 2 +- .../readme-ressources/Multiprocess.png | Bin 52653 -> 0 bytes .../Tests/Runtime/MultiprocessRuntime/readme.md | 4 ++-- 7 files changed, 4 insertions(+), 4 deletions(-) create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.jpg rename testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/{Building-Player.png.meta => Building-Player.jpg.meta} (98%) delete mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png create mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.jpg rename testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/{Multiprocess.png.meta => Multiprocess.jpg.meta} (98%) delete mode 100644 testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.jpg b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.jpg new file mode 100644 index 0000000000000000000000000000000000000000..8663fcf7577639caf0bb5e94fa8c67306aee5fb5 GIT binary patch literal 11346 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 DL-f!q literal 0 HcmV?d00001 diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.jpg.meta similarity index 98% rename from testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png.meta rename to testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.jpg.meta index d99923173c..e675f9e356 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png.meta +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.jpg.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 908a65855708a46d09689a292cc4250b +guid: 4d67ff4fda6ec4805b2fd16af441aa47 TextureImporter: internalIDToNameTable: [] externalObjects: {} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Building-Player.png deleted file mode 100644 index d731ed8255d60a5d63f02d055da8f88a410bdbc1..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 35626 zcmZVl1ymeCvo{Xo?u6hh4hasyVR5&h!JXg|oCH|hVX@$_SRlb&gS!O?E(z`sV9`Z( z|2+4-?|bk0zB#97YP#xIHC^2^r@N~r_PwSOJ`NQQ5)u-=in6>85)$(93mt`p@p2~N z9IJh)P;iu!d#@rVNB`c_{iCCc9TF0IijAcui3%tCsFjta<>(a08yrtRotT&e9ZS%7 z@2}pz-m${r-n1-J(@hGZO;o*pq!R7ER%g5f`iBPY(W=BEYVNE&&5sY$?;d5pPNZpN z9cv?fP<8smpA1JG{4s{Ez=DUD@J71DGe8A7<`1%M{3~QyblYj<`Z^&OWkhH-NViS#c2&hYT6=HLhf&9C0xnz7!C zy9WfKrxk%f_)(&wUU6YWp>7}%iCAq$U;Tx?_|?G9Q01eB2GZLX8Vd1jrQT{I%`M41E|I$cN{}GhYl~Yl9x$4?@+S$2zIkuznw?eFUG9|)3!zt{`uYUgc5@9*m3<|XDY$@pIqVlVW6 zs(Bdc|4YQ%S(4FE<2}8cyQdw!5H~M3FC!3#o}OO9)Apm7j=bW3!(Z+s86CX6J;Zo; z{QUg5{RFt(J?(k;L`6k;c=>tw`MF*sxV!?~ysiAX+`O3nkCOkZN8ZlM#?#Tm+tJ;P z{y%!HtlfRQB^eq2W9a`K|K~aF{2l+tlAG6mH|wQ=JpbYF@Nx6<{6F0

KK4ioJLA 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 NZdm*?q5+%_|34=f;>-X5 literal 0 HcmV?d00001 diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.jpg.meta similarity index 98% rename from testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta rename to testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.jpg.meta index 3a3610583a..6d73b54a8f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png.meta +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.jpg.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: 7a4260b05936b43349341676f94db6a8 +guid: d2e6ce5793e6a4e74843dca06fd36778 TextureImporter: internalIDToNameTable: [] externalObjects: {} diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme-ressources/Multiprocess.png deleted file mode 100644 index 7e9d22a205dd3a5bc7cae6aa9d5795eb101390f9..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 52653 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 PqCIVuq0lxx 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 E0NOKZCIA2c diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 975fb5fed7..fd44aeebb0 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -175,13 +175,13 @@ namespace MLAPI.MultiprocessRuntimeTests ## How to run a test locally Test players need to be built first to test locally. Integration with CI should do this automatically. -![](readme-ressources/Building-Player.png) +![](readme-ressources/Building-Player.jpg) Then run the tests. Performance tests should only be run from external processes (not from editor). This way the server code will run in a build, just as much as client code. -![](readme-ressources/Multiprocess.png) +![](readme-ressources/Multiprocess.jpg) ## How it's done ### Multiple processes orchestration From 6b5b03fb1747c74ff0f3e0257097af9bf7a559e3 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:55:21 -0400 Subject: [PATCH 49/66] # --- .../Assets/Tests/Runtime/MultiprocessRuntime/readme.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index fd44aeebb0..0fb81f18cf 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -66,8 +66,7 @@ Another way to write a multiprocess test without context based steps is to use T } ``` -If you want to pass in dynamic test parameters (for example nunit `Values`), you need to pass them as a `byte[]` parameter to your step, since remote execution won't have context capture from the test execution and you won't see the test's parameters. - +Here's a complete set of examples using the API ```C# using //... @@ -97,6 +96,7 @@ namespace MLAPI.MultiprocessRuntimeTests Assert.That(1, Is.EqualTo(1)); throw new Exception("asdf"); // this client side exception will be communicated to the coordinator, making the test fail }); + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { // To write results to the test runner, call this method: @@ -120,7 +120,6 @@ namespace MLAPI.MultiprocessRuntimeTests } }); - int someValue = 456; // one caveat to executeStepInContext is contrary to instinct, this is not shared between server and client execution. // to send that value to clients, "paramToPass" needs to be used yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => @@ -139,6 +138,7 @@ namespace MLAPI.MultiprocessRuntimeTests } NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; }, waitMultipleUpdates: true); // this keeps waiting "are you done? are you done? are you done?" and relies on the clients calling the "ClientFinishedServerRpc" + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { int cpt = 0; From 97c9ee8084ebb899477055bbf6ea12a134ac1af9 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:57:57 -0400 Subject: [PATCH 50/66] # --- .../Assets/Tests/Runtime/MultiprocessRuntime/readme.md | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 0fb81f18cf..55094bd1f7 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -172,14 +172,16 @@ namespace MLAPI.MultiprocessRuntimeTests ``` -## How to run a test locally -Test players need to be built first to test locally. Integration with CI should do this automatically. +## How to run a test +**Local**: Test players need to be built first to test locally. + +**Automated**: Integration with CI should do this automatically. ![](readme-ressources/Building-Player.jpg) -Then run the tests. +Then run the tests from Unity's test runner. -Performance tests should only be run from external processes (not from editor). This way the server code will run in a build, just as much as client code. +Note that performance tests should be run from external processes (not from editor). This way the server code will run in a build, just as much as client code, for more realistic test results. ![](readme-ressources/Multiprocess.jpg) From e5c9728317e1dbbf4c422a5ed5ba353696ef26a8 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 16:58:26 -0400 Subject: [PATCH 51/66] # --- testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 55094bd1f7..e1a9ee218d 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -192,7 +192,7 @@ With the bokken integration, we'll need to be careful about ressource contention Tests when launched locally will simply create new OS processes for each worker players. ![](readme-ressources/OrchestrationOverview.jpg) -*Note that this diagram is still WIP* +*Note that this diagram is still WIP for the CI part* ### Bokken orchestration todo ### CI From 070f6afcd078bb6772b50218f341b9d606e4c2f3 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 21:58:18 -0400 Subject: [PATCH 52/66] formatting issues --- .../BaseMultiprocessTests.cs | 20 ++++++++++------- .../Helpers/BuildMultiprocessTestPlayer.cs | 2 +- .../Helpers/MultiprocessOrchestration.cs | 1 - .../MultiprocessRuntime/TestCoordinator.cs | 22 +++++++------------ .../TestCoordinatorTests.cs | 17 +++++++------- 5 files changed, 29 insertions(+), 33 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs index 501920e9a8..ace3f408dc 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -9,19 +9,22 @@ namespace MLAPI.MultiprocessRuntimeTests { public class MultiprocessTestsAttribute : CategoryAttribute { - public const string multiprocessCategoryName = "Multiprocess"; - public MultiprocessTestsAttribute() : base(multiprocessCategoryName){} + public const string MultiprocessCategoryName = "Multiprocess"; + public MultiprocessTestsAttribute() : base(MultiprocessCategoryName) { } } [MultiprocessTests] public abstract class BaseMultiprocessTests { - protected virtual bool m_IsPerformanceTest => true; private bool ShouldIgnoreTests => m_IsPerformanceTest && Application.isEditor; - protected abstract int NbWorkers { get; } + ///

+ /// Implement this to specify the amount of workers to spawn from your main test runner + /// TODO there's a good chance this will be refactored with something fancier once we start integrating with bokken + /// + protected abstract int WorkerCount { get; } [OneTimeSetUp] public virtual void SetupTestSuite() @@ -34,7 +37,7 @@ public virtual void SetupTestSuite() SceneManager.LoadScene(BuildMultiprocessTestPlayer.MainSceneName, LoadSceneMode.Single); SceneManager.sceneLoaded += OnSceneLoaded; - for (int i = 0; i < NbWorkers; i++) + for (int i = 0; i < WorkerCount; i++) { MultiprocessOrchestration.StartWorkerNode(); // will automatically start built player as clients } @@ -52,15 +55,16 @@ public virtual IEnumerator Setup() yield return new WaitUntil(() => NetworkManager.Singleton != null && NetworkManager.Singleton.IsServer); var startTime = Time.time; - while (NetworkManager.Singleton.ConnectedClients.Count <= NbWorkers) + while (NetworkManager.Singleton.ConnectedClients.Count <= WorkerCount) { yield return new WaitForSeconds(0.2f); - if (Time.time - startTime > TestCoordinator.maxWaitTimeout) + if (Time.time - startTime > TestCoordinator.MaxWaitTimeoutSec) { - throw new Exception($"waiting too long to see clients to connect, got {NetworkManager.Singleton.ConnectedClients.Count - 1} clients, but was expecting {NbWorkers}, failing"); + throw new Exception($"waiting too long to see clients to connect, got {NetworkManager.Singleton.ConnectedClients.Count - 1} clients, but was expecting {WorkerCount}, failing"); } } + TestCoordinator.Instance.KeepAliveClientRpc(); } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index 77f877a115..4bfc7ab20f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -15,7 +15,7 @@ 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"); + private static string BuildPathDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", "MultiprocessTests"); public static string BuildPath => Path.Combine(BuildPathDirectory, "MultiprocessTestPlayer"); #if UNITY_EDITOR diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 18196d7954..b09ee09914 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -2,7 +2,6 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; -using UnityEditor; using UnityEngine; using Debug = UnityEngine.Debug; diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 7cf323fb73..61cbbbb234 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -1,17 +1,11 @@ using System; -using System.Collections; using System.Collections.Generic; -using System.Diagnostics; using System.Linq; using System.Reflection; using MLAPI; -using MLAPI.Configuration; -using MLAPI.Logging; using MLAPI.Messaging; using NUnit.Framework; -using NUnit.Framework.Interfaces; using UnityEngine; -using UnityEngine.TestTools; using Debug = UnityEngine.Debug; /// @@ -27,12 +21,11 @@ [RequireComponent(typeof(NetworkObject))] public class TestCoordinator : NetworkBehaviour { - public const int perTestTimeout = 5 * 60; // seconds + public const int PerTestTimeoutSec = 5 * 60; // seconds - public const float maxWaitTimeout = 20; + public const float MaxWaitTimeoutSec = 20; private const char k_MethodFullNameSplitChar = '@'; - private bool m_ShouldShutdown; private float m_TimeSinceLastConnected; private float m_TimeSinceLastKeepAlive; @@ -49,7 +42,7 @@ private void Awake() { if (Instance != null) { - Debug.LogError("Multiple test coordinator! destroying self"); + Debug.LogError("Multiple test coordinator, destroying this instance"); Destroy(gameObject); return; } @@ -73,16 +66,17 @@ public void Start() public void Update() { - if (Time.time - m_TimeSinceLastKeepAlive > perTestTimeout) + if (Time.time - m_TimeSinceLastKeepAlive > PerTestTimeoutSec) { QuitApplication(); Assert.Fail("Stayed idle too long"); } + if ((IsServer && NetworkManager.Singleton.IsListening) || (IsClient && NetworkManager.Singleton.IsConnectedClient)) { m_TimeSinceLastConnected = Time.time; } - else if (Time.time - m_TimeSinceLastConnected > maxWaitTimeout || m_ShouldShutdown) + else if (Time.time - m_TimeSinceLastConnected > MaxWaitTimeoutSec || m_ShouldShutdown) { // Make sure we don't have zombie processes Debug.Log($"quitting application, shouldShutdown set to {m_ShouldShutdown}, is listening {NetworkManager.Singleton.IsListening}, is connected client {NetworkManager.Singleton.IsConnectedClient}"); @@ -182,7 +176,7 @@ public static Func ResultIsSet(bool useTimeoutException = true) var startWaitTime = Time.time; return () => { - if (Time.time - startWaitTime > maxWaitTimeout) + if (Time.time - startWaitTime > MaxWaitTimeoutSec) { if (useTimeoutException) { @@ -209,7 +203,7 @@ public static Func ConsumeClientIsFinished(ulong clientId, bool useTimeout var startWaitTime = Time.time; return () => { - if (Time.time - startWaitTime > maxWaitTimeout) + if (Time.time - startWaitTime > MaxWaitTimeoutSec) { if (useTimeoutException) { diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs index 4c22ca1d21..79d53ed053 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinatorTests.cs @@ -1,4 +1,3 @@ -using System; using System.Collections; using System.Linq; using NUnit.Framework; @@ -11,14 +10,14 @@ namespace MLAPI.MultiprocessRuntimeTests [TestFixture(2)] public class TestCoordinatorTests : BaseMultiprocessTests { - private int m_NbWorkers; - protected override int NbWorkers => m_NbWorkers; + private int m_WorkerCount; + protected override int WorkerCount => m_WorkerCount; protected override bool m_IsPerformanceTest => false; - public TestCoordinatorTests(int nbWorkers) + public TestCoordinatorTests(int workerCount) { - m_NbWorkers = nbWorkers; + m_WorkerCount = workerCount; } private static void ExecuteSimpleCoordinatorTest() @@ -39,7 +38,7 @@ public IEnumerator CheckTestCoordinator() TestCoordinator.Instance.InvokeFromMethodActionRpc(ExecuteSimpleCoordinatorTest); var nbResults = 0; - for (int i = 0; i < NbWorkers; i++) // wait and test for the two clients + for (int i = 0; i < WorkerCount; i++) // wait and test for the two clients { yield return new WaitUntil(TestCoordinator.ResultIsSet()); @@ -47,7 +46,7 @@ public IEnumerator CheckTestCoordinator() Assert.Greater(result, 0f); nbResults++; } - Assert.That(nbResults, Is.EqualTo(NbWorkers)); + Assert.That(nbResults, Is.EqualTo(WorkerCount)); } [UnityTest] @@ -56,7 +55,7 @@ public IEnumerator CheckTestCoordinatorWithArgs() TestCoordinator.Instance.InvokeFromMethodActionRpc(ExecuteWithArgs, 99); var nbResults = 0; - for (int i = 0; i < NbWorkers; i++) // wait and test for the two clients + for (int i = 0; i < WorkerCount; i++) // wait and test for the two clients { yield return new WaitUntil(TestCoordinator.ResultIsSet()); @@ -64,7 +63,7 @@ public IEnumerator CheckTestCoordinatorWithArgs() Assert.That(result, Is.EqualTo(99)); nbResults++; } - Assert.That(nbResults, Is.EqualTo(NbWorkers)); + Assert.That(nbResults, Is.EqualTo(WorkerCount)); } } } From d29cc805a89cc3f5df85d1e5ae4de0c98de14f8a Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 22:03:39 -0400 Subject: [PATCH 53/66] format fix --- .../NetworkVariablePerformanceTests.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index 63b140e36a..ae4d2568f8 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -15,7 +15,7 @@ namespace MLAPI.MultiprocessRuntimeTests { public class NetworkVariablePerformanceTests : BaseMultiprocessTests { - protected override int NbWorkers { get; } = 1; + protected override int WorkerCount { get; } = 1; private const int k_MaxObjectsToSpawn = 10000; private List m_ServerSpawnedObjects = new List(); private static GameObjectPool s_ServerObjectPool; @@ -140,7 +140,7 @@ void Update(float deltaTime) { // wait for spawn results coming from clients int finishedCount = 0; - if (TestCoordinator.AllClientIdsWithResults.Count != NbWorkers) + if (TestCoordinator.AllClientIdsWithResults.Count != WorkerCount) { return false; } @@ -154,7 +154,7 @@ void Update(float deltaTime) } } - return finishedCount == NbWorkers; + return finishedCount == WorkerCount; }); float serverLastResult = 0; yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => @@ -169,7 +169,7 @@ void Update(float deltaTime) Assert.That(lastResult, Is.EqualTo(nbObjects)); } - Assert.That(TestCoordinator.AllClientIdsWithResults.Count, Is.EqualTo(NbWorkers)); + Assert.That(TestCoordinator.AllClientIdsWithResults.Count, Is.EqualTo(WorkerCount)); foreach (var (clientId, result) in TestCoordinator.ConsumeCurrentResult()) { Measure.Custom(allocated, result); From 8f8f7ec549d8f5e398551aa33789fa969e518a8c Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 22:04:04 -0400 Subject: [PATCH 54/66] name fix --- .../Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs | 2 +- .../Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs index fedba34887..3cc40ffbfe 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -57,7 +57,7 @@ public IEnumerator AfterTest(ITest test) private bool m_IgnoreTimeoutException; private float m_StartTime; - private bool isTimingOut => Time.time - m_StartTime > TestCoordinator.maxWaitTimeout; + private bool isTimingOut => Time.time - m_StartTime > TestCoordinator.MaxWaitTimeoutSec; private bool ShouldExecuteLocally => (m_ActionContext == StepExecutionContext.Server && m_NetworkManager.IsServer) || (m_ActionContext == StepExecutionContext.Clients && !m_NetworkManager.IsServer); public static bool isRegistering; diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs index be6d869e75..3b56a3908f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs @@ -20,7 +20,7 @@ public ExecuteStepInContextTests(int nbWorkersToTest) { m_NbWorkersToTest = nbWorkersToTest; } - protected override int NbWorkers => m_NbWorkersToTest; + protected override int WorkerCount => m_NbWorkersToTest; protected override bool m_IsPerformanceTest => false; [UnityTest, MultiprocessContextBasedTest] @@ -214,7 +214,7 @@ public IEnumerator TestExecuteInContext() Assert.AreEqual(12345, res.result); } - Assert.That(resultCountFromWorkers, Is.EqualTo(NbWorkers)); + Assert.That(resultCountFromWorkers, Is.EqualTo(WorkerCount)); }); const int timeToWait = 4; From 9d33c8c9ee1304c7a9bbdd00fb2fb696f5d75d00 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 22:07:17 -0400 Subject: [PATCH 55/66] # --- testproject/Packages/packages-lock.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index 66e5bf626c..f82742c76d 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -121,7 +121,7 @@ "url": "https://packages.unity.com" }, "com.unity.test-framework": { - "version": "1.1.26", + "version": "1.1.27", "depth": 0, "source": "registry", "dependencies": { From 58975611f85eca2bd5e11be951d3f347166edb55 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:00:08 -0400 Subject: [PATCH 56/66] format update --- .../ExecuteStepInContext.cs | 70 +++++++++---------- .../ExecuteStepInContextTests.cs | 64 +++++++++-------- .../MultiprocessRuntime/TestCoordinator.cs | 8 +-- 3 files changed, 73 insertions(+), 69 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs index 3cc40ffbfe..5540ba3cdf 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContext.cs @@ -31,7 +31,7 @@ public class MultiprocessContextBasedTestAttribute : NUnitAttribute, IOuterUnity { public IEnumerator BeforeTest(ITest test) { - yield return new WaitUntil(() => TestCoordinator.Instance != null && hasRegistered); + yield return new WaitUntil(() => TestCoordinator.Instance != null && HasRegistered); } public IEnumerator AfterTest(ITest test) @@ -42,11 +42,11 @@ public IEnumerator AfterTest(ITest test) private StepExecutionContext m_ActionContext; private Action m_StepToExecute; - private string m_CurrentActionID; + private string m_CurrentActionId; // as a remote worker, I store all available actions so I can execute them when triggered from RPCs - public static Dictionary allActions = new Dictionary(); - private static Dictionary s_MethodIDCounter = new Dictionary(); + public static Dictionary AllActions = new Dictionary(); + private static Dictionary s_MethodIdCounter = new Dictionary(); private NetworkManager m_NetworkManager; private bool m_IsRegistering; @@ -58,11 +58,11 @@ public IEnumerator AfterTest(ITest test) private float m_StartTime; private bool isTimingOut => Time.time - m_StartTime > TestCoordinator.MaxWaitTimeoutSec; - private bool ShouldExecuteLocally => (m_ActionContext == StepExecutionContext.Server && m_NetworkManager.IsServer) || (m_ActionContext == StepExecutionContext.Clients && !m_NetworkManager.IsServer); + private bool shouldExecuteLocally => (m_ActionContext == StepExecutionContext.Server && m_NetworkManager.IsServer) || (m_ActionContext == StepExecutionContext.Clients && !m_NetworkManager.IsServer); - public static bool isRegistering; - public static bool hasRegistered; - private static List m_AllClientTestInstances = new List(); // to keep an instance for each tests, so captured context in each step is kept + public static bool IsRegistering; + public static bool HasRegistered; + private static List s_AllClientTestInstances = new List(); // to keep an instance for each tests, so captured context in each step is kept /// /// This MUST be called at the beginning of each test in order to use context based steps. @@ -70,11 +70,11 @@ public IEnumerator AfterTest(ITest test) /// even with the same method name and different parameters). /// This relies on the name to be unique for each generated IEnumerator state machines /// - public static void InitContextSteps() + public static void InitializeContextSteps() { var callerMethod = new StackFrame(1).GetMethod(); var methodIdentifier = GetMethodIdentifier(callerMethod); // since this is called from IEnumerator, this should be a generated class, making it unique - s_MethodIDCounter[methodIdentifier] = 0; + s_MethodIdCounter[methodIdentifier] = 0; } private static string GetMethodIdentifier(MethodBase callerMethod) @@ -85,11 +85,11 @@ private static string GetMethodIdentifier(MethodBase callerMethod) internal static void InitializeAllSteps() { // registering magically all context based steps - isRegistering = true; + IsRegistering = true; var registeredMethods = typeof(TestCoordinator).Assembly.GetTypes().SelectMany(t => t.GetMethods()) - .Where(m => m.GetCustomAttributes(typeof(ExecuteStepInContext.MultiprocessContextBasedTestAttribute), true).Length > 0) + .Where(m => m.GetCustomAttributes(typeof(MultiprocessContextBasedTestAttribute), true).Length > 0) .ToArray(); - HashSet typesWithContextMethods = new HashSet(); + var typesWithContextMethods = new HashSet(); foreach (var method in registeredMethods) { typesWithContextMethods.Add(method.ReflectedType); @@ -97,10 +97,10 @@ internal static void InitializeAllSteps() if (registeredMethods.Length == 0) { - throw new Exception("Couldn't find any registered methods for multiprocess testing. Is TestCoordinator in same assembly as test methods?"); + throw new Exception($"Couldn't find any registered methods for multiprocess testing. Is {nameof(TestCoordinator)} in same assembly as test methods?"); } - object[] GetParameterValuesToPass(ParameterInfo[] parameterInfo) + object[] GetParameterValuesToPassFunc(ParameterInfo[] parameterInfo) { object[] parametersToReturn = new object[parameterInfo.Length]; for (int i = 0; i < parameterInfo.Length; i++) @@ -123,16 +123,16 @@ object[] GetParameterValuesToPass(ParameterInfo[] parameterInfo) var allConstructors = contextType.GetConstructors(); if (allConstructors.Length > 1) { - throw new NotImplementedException("Case not implemented where test has more than one contructor"); + throw new NotImplementedException("Case not implemented where test has more than one constructor"); } - var instance = Activator.CreateInstance(contextType, allConstructors.Length > 0 ? GetParameterValuesToPass(allConstructors[0].GetParameters()) : null); - m_AllClientTestInstances.Add(instance); // keeping that instance so tests can use captured local attributes + var instance = Activator.CreateInstance(contextType, allConstructors.Length > 0 ? GetParameterValuesToPassFunc(allConstructors[0].GetParameters()) : null); + s_AllClientTestInstances.Add(instance); // keeping that instance so tests can use captured local attributes - List typeMethodsWithContextSteps = new List(); + var typeMethodsWithContextSteps = new List(); foreach (var method in contextType.GetMethods()) { - if (method.GetCustomAttributes(typeof(ExecuteStepInContext.MultiprocessContextBasedTestAttribute), true).Length > 0) + if (method.GetCustomAttributes(typeof(MultiprocessContextBasedTestAttribute), true).Length > 0) { typeMethodsWithContextSteps.Add(method); } @@ -140,14 +140,14 @@ object[] GetParameterValuesToPass(ParameterInfo[] parameterInfo) foreach (var method in typeMethodsWithContextSteps) { - var parametersToPass = GetParameterValuesToPass(method.GetParameters()); + var parametersToPass = GetParameterValuesToPassFunc(method.GetParameters()); var enumerator = (IEnumerator)method.Invoke(instance, parametersToPass.ToArray()); while (enumerator.MoveNext()) { } } } - isRegistering = false; - hasRegistered = true; + IsRegistering = false; + HasRegistered = true; } /// @@ -164,7 +164,7 @@ object[] GetParameterValuesToPass(ParameterInfo[] parameterInfo) public ExecuteStepInContext(StepExecutionContext actionContext, Action stepToExecute, bool ignoreTimeoutException = false, byte[] paramToPass = default, NetworkManager networkManager = null, bool waitMultipleUpdates = false, Func additionalIsFinishedWaiter = null) { m_StartTime = Time.time; - m_IsRegistering = isRegistering; + m_IsRegistering = IsRegistering; m_ActionContext = actionContext; m_StepToExecute = stepToExecute; m_WaitMultipleUpdates = waitMultipleUpdates; @@ -185,22 +185,22 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s var callerMethod = new StackFrame(1).GetMethod(); // one skip frame for current method var methodId = GetMethodIdentifier(callerMethod); // assumes called from IEnumerator MoveNext, which should be the case since we're a CustomYieldInstruction. This will return a generated class name which should be unique - if (!s_MethodIDCounter.ContainsKey(methodId)) + if (!s_MethodIdCounter.ContainsKey(methodId)) { - s_MethodIDCounter[methodId] = 0; + s_MethodIdCounter[methodId] = 0; } - string currentActionID = $"{methodId}-{s_MethodIDCounter[methodId]++}"; - m_CurrentActionID = currentActionID; + string currentActionId = $"{methodId}-{s_MethodIdCounter[methodId]++}"; + m_CurrentActionId = currentActionId; if (m_IsRegistering) { - Assert.That(allActions, Does.Not.Contain(currentActionID)); // sanity check - allActions[currentActionID] = this; + Assert.That(AllActions, Does.Not.Contain(currentActionId)); // sanity check + AllActions[currentActionId] = this; } else { - if (ShouldExecuteLocally) + if (shouldExecuteLocally) { m_StepToExecute.Invoke(paramToPass); } @@ -208,7 +208,7 @@ public ExecuteStepInContext(StepExecutionContext actionContext, Action s { if (networkManager.IsServer) { - TestCoordinator.Instance.TriggerActionIDClientRpc(currentActionID, paramToPass, + TestCoordinator.Instance.TriggerActionIdClientRpc(currentActionId, paramToPass, clientRpcParams: new ClientRpcParams { Send = new ClientRpcSendParams { TargetClientIds = TestCoordinator.AllClientIdsExceptMine.ToArray() } @@ -250,11 +250,11 @@ public override bool keepWaiting { if (m_IgnoreTimeoutException) { - Debug.LogWarning($"Timeout ignored for action ID {m_CurrentActionID}"); + Debug.LogWarning($"Timeout ignored for action ID {m_CurrentActionId}"); return false; } - throw new Exception($"timeout for Context Step with action ID {m_CurrentActionID}"); + throw new Exception($"timeout for Context Step with action ID {m_CurrentActionId}"); } if (m_AdditionalIsFinishedWaiter != null) @@ -266,7 +266,7 @@ public override bool keepWaiting } } - if (m_IsRegistering || ShouldExecuteLocally || m_ClientIsFinishedChecks == null) + if (m_IsRegistering || shouldExecuteLocally || m_ClientIsFinishedChecks == null) { return false; } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs index 3b56a3908f..d351cbb99d 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/ExecuteStepInContextTests.cs @@ -15,20 +15,22 @@ namespace MLAPI.MultiprocessRuntimeTests [TestFixture(2)] public class ExecuteStepInContextTests : BaseMultiprocessTests { - private int m_NbWorkersToTest; - public ExecuteStepInContextTests(int nbWorkersToTest) + private int m_WorkerCountToTest; + + public ExecuteStepInContextTests(int workerCountToTest) { - m_NbWorkersToTest = nbWorkersToTest; + m_WorkerCountToTest = workerCountToTest; } - protected override int WorkerCount => m_NbWorkersToTest; + + protected override int WorkerCount => m_WorkerCountToTest; protected override bool m_IsPerformanceTest => false; [UnityTest, MultiprocessContextBasedTest] - public IEnumerator TestWithSameName([Values(1)]int a) + public IEnumerator TestWithSameName([Values(1)] int a) { // ExecuteStepInContext bases itself on method name to identify steps. We need to make sure that methods with // same names, but different signatures behave correctly - InitContextSteps(); + InitializeContextSteps(); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { Assert.That(a, Is.EqualTo(1)); @@ -40,9 +42,9 @@ public IEnumerator TestWithSameName([Values(1)]int a) } [UnityTest, MultiprocessContextBasedTest] - public IEnumerator TestWithSameName([Values(2)]int a, [Values(3)]int b) + public IEnumerator TestWithSameName([Values(2)] int a, [Values(3)] int b) { - InitContextSteps(); + InitializeContextSteps(); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { Assert.That(b, Is.EqualTo(3)); @@ -56,7 +58,7 @@ public IEnumerator TestWithSameName([Values(2)]int a, [Values(3)]int b) [UnityTest, MultiprocessContextBasedTest] public IEnumerator TestWithParameters([Values(1, 2, 3)] int a) { - InitContextSteps(); + InitializeContextSteps(); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { @@ -78,7 +80,7 @@ public IEnumerator TestWithParameters([Values(1, 2, 3)] int a) [TestCase(3, 4, ExpectedResult = null)] public IEnumerator TestWithParameters(int a, int b) { - InitContextSteps(); + InitializeContextSteps(); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { @@ -99,7 +101,7 @@ public IEnumerator TestWithParameters(int a, int b) [UnityTest, MultiprocessContextBasedTest] public IEnumerator TestExceptionClientSide() { - InitContextSteps(); + InitializeContextSteps(); const string exceptionMessageToTest = "This is an exception for TestCoordinator that's expected"; yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => @@ -108,7 +110,7 @@ public IEnumerator TestExceptionClientSide() }, ignoreTimeoutException: true); yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => { - for (int i = 0; i < m_NbWorkersToTest; i++) + for (int i = 0; i < m_WorkerCountToTest; i++) { LogAssert.Expect(LogType.Error, new Regex($".*{exceptionMessageToTest}.*")); } @@ -117,17 +119,17 @@ public IEnumerator TestExceptionClientSide() const string exceptionUpdateMessageToTest = "This is an exception for update loop client side that's expected"; yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => { - void Update(float __) + void UpdateFunc(float _) { - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateFunc; throw new Exception(exceptionUpdateMessageToTest); } - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateFunc; }, ignoreTimeoutException: true); yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => { - for (int i = 0; i < m_NbWorkersToTest; i++) + for (int i = 0; i < m_WorkerCountToTest; i++) { LogAssert.Expect(LogType.Error, new Regex($".*{exceptionUpdateMessageToTest}.*")); } @@ -137,45 +139,47 @@ void Update(float __) [UnityTest, MultiprocessContextBasedTest] public IEnumerator ContextTestWithAdditionalWait() { - InitContextSteps(); + InitializeContextSteps(); const int maxValue = 10; yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => { int count = 0; - void Update(float __) + void UpdateFunc(float _) { TestCoordinator.Instance.WriteTestResultsServerRpc(count++); if (count > maxValue) { - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateFunc; } } - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateFunc; }, additionalIsFinishedWaiter: () => { int nbFinished = 0; - for (int i = 0; i < m_NbWorkersToTest; i++) + for (int i = 0; i < m_WorkerCountToTest; i++) { if (TestCoordinator.PeekLatestResult(TestCoordinator.AllClientIdsExceptMine[i]) == maxValue) { nbFinished++; } } - return nbFinished == m_NbWorkersToTest; + + return nbFinished == m_WorkerCountToTest; }); yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => { - Assert.That(TestCoordinator.AllClientIdsExceptMine.Count, Is.EqualTo(m_NbWorkersToTest)); - foreach (var clientID in TestCoordinator.AllClientIdsExceptMine) + Assert.That(TestCoordinator.AllClientIdsExceptMine.Count, Is.EqualTo(m_WorkerCountToTest)); + foreach (var clientId in TestCoordinator.AllClientIdsExceptMine) { var current = 0; - foreach (var res in TestCoordinator.ConsumeCurrentResult(clientID)) + foreach (var res in TestCoordinator.ConsumeCurrentResult(clientId)) { Assert.That(res, Is.EqualTo(current++)); } + Assert.That(current - 1, Is.EqualTo(maxValue)); } }); @@ -184,7 +188,7 @@ void Update(float __) [UnityTest, MultiprocessContextBasedTest] public IEnumerator TestExecuteInContext() { - InitContextSteps(); + InitializeContextSteps(); int stepCountExecuted = 0; yield return new ExecuteStepInContext(StepExecutionContext.Server, args => @@ -220,18 +224,18 @@ public IEnumerator TestExecuteInContext() const int timeToWait = 4; yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => { - void Update(float __) + void UpdateFunc(float _) { if (Time.time > timeToWait) { - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateFunc; TestCoordinator.Instance.WriteTestResultsServerRpc(Time.time); TestCoordinator.Instance.ClientFinishedServerRpc(); // since finishOnInvoke is false, we need to do this manually } } - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateFunc; }, waitMultipleUpdates: true); // waits multiple frames before allowing the next action to continue. yield return new ExecuteStepInContext(StepExecutionContext.Server, args => @@ -247,7 +251,7 @@ void Update(float __) Assert.Greater(count, 0); }); - if (!isRegistering) + if (!IsRegistering) { Assert.AreEqual(3, stepCountExecuted); } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index b8f11a8a0a..33396b0ec6 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -143,9 +143,9 @@ private static string GetMethodInfo(Action method) } } - public static IEnumerable ConsumeCurrentResult(ulong clientID) + public static IEnumerable ConsumeCurrentResult(ulong clientId) { - var allResults = Instance.m_TestResultsLocal[clientID]; + var allResults = Instance.m_TestResultsLocal[clientId]; while (allResults.Count > 0) { var toReturn = allResults[0]; @@ -245,9 +245,9 @@ public void InvokeFromMethodActionRpc(Action methodInfo) } [ClientRpc] - public void TriggerActionIDClientRpc(string actionID, byte[] args, ClientRpcParams clientRpcParams = default) + public void TriggerActionIdClientRpc(string actionId, byte[] args, ClientRpcParams clientRpcParams = default) { - Debug.Log($"received RPC from server, client side triggering action ID {actionID}"); + Debug.Log($"received RPC from server, client side triggering action ID {actionId}"); try { ExecuteStepInContext.allActions[actionID].Invoke(args); From e64b12e22b98c5579f0f8751e48df2ba4aece032 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:01:41 -0400 Subject: [PATCH 57/66] name fix --- .../Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 61cbbbb234..fc0efec2e1 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -250,7 +250,7 @@ public void TriggerActionIDClientRpc(string actionID, byte[] args, ClientRpcPara Debug.Log($"received RPC from server, client side triggering action ID {actionID}"); try { - // ExecuteStepInContext.allActions[actionID].Invoke(args); + //ExecuteStepInContext.AllActions[actionId].Invoke(args); } catch (Exception e) { From dfb97b2d90c9c75298c27f1da8697d691af70488 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:19:20 -0400 Subject: [PATCH 58/66] format issues --- .../PrefabReference.cs | 3 +- .../Helpers/BuildMultiprocessTestPlayer.cs | 63 +++++++++---------- .../CustomPrefabSpawnerForPerformanceTests.cs | 4 +- .../Helpers/GameObjectPool.cs | 3 +- .../Helpers/MultiprocessOrchestration.cs | 2 +- .../NetworkVariablePerformanceTests.cs | 40 ++++++------ 6 files changed, 54 insertions(+), 61 deletions(-) diff --git a/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs index b03408e505..aba19f2f2a 100644 --- a/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs +++ b/testproject/Assets/Scripts/ScriptsForAutomatedTesting/PrefabReference.cs @@ -6,7 +6,7 @@ public class PrefabReference : MonoBehaviour { [SerializeField] - public GameObject referencedPrefab; + public GameObject ReferencedPrefab; public static PrefabReference Instance { get; private set; } @@ -17,6 +17,7 @@ public void Awake() Destroy(gameObject); return; } + Instance = this; } } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs index bbd1b3e348..faeb884bfd 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/BuildMultiprocessTestPlayer.cs @@ -14,62 +14,58 @@ namespace MLAPI.MultiprocessRuntimeTests /// 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"; + public const string MultiprocessBaseMenuName = "MLAPI/Multiprocess Test"; + public const string BuildAndExecuteMenuName = MultiprocessBaseMenuName + "/Build Test Player #t"; + public const string MainSceneName = "MultiprocessTestScene"; public const string BuildInfoFileName = "buildInfo.txt"; - private static string BuildPathDirectory => Path.Combine(Path.GetDirectoryName(Application.dataPath), "Builds", "MultiprocessTests"); - public static string BuildPath => Path.Combine(BuildPathDirectory, "MultiprocessTestPlayer"); + 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() + [MenuItem(BuildAndExecuteMenuName)] + public static void BuildRelease() { - var report = BuildPlayer(); - if (report.summary.result != BuildResult.Succeeded) + var report = BuildPlayer(); + if (report.summary.result != BuildResult.Succeeded) { - throw new Exception($"Build failed! {report.summary.totalErrors} errors"); + throw new Exception($"Build failed! {report.summary.totalErrors} errors"); } } - [MenuItem(MultiprocessBaseMenuName + "/Build Test Player (Debug)")] + [MenuItem(MultiprocessBaseMenuName + "/Build Test Player (Debug)")] public static void BuildDebug() { - var report = BuildPlayer(true); - if (report.summary.result != BuildResult.Succeeded) + var report = BuildPlayer(true); + if (report.summary.result != BuildResult.Succeeded) { - throw new Exception($"Build failed! {report.summary.totalErrors} errors"); + throw new Exception($"Build failed! {report.summary.totalErrors} errors"); } } - [MenuItem(MultiprocessBaseMenuName+"/Delete Test Build")] + [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"); - + 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) + 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) - SaveBuildInfo(new BuildInfo() { buildPath = BuildPath, isDebug = isDebug }); + SaveBuildInfo(new BuildInfo() { BuildPath = BuildPath, IsDebug = isDebug }); // deleting so we don't end up testing on outdated builds if there's a build failure DeleteBuild(); @@ -90,7 +86,8 @@ private static BuildReport BuildPlayer(bool isDebug = false) { buildPathToUse += ".exe"; } - Debug.Log($"Starting multiprocess player build using path {buildPathToUse}"); + + Debug.Log($"Starting multiprocess player build using path {buildPathToUse}"); buildOptions &= ~BuildOptions.AutoRunPlayer; var buildReport = BuildPipeline.BuildPlayer( @@ -99,16 +96,16 @@ private static BuildReport BuildPlayer(bool isDebug = false) EditorUserBuildSettings.activeBuildTarget, buildOptions); - Debug.Log("Build finished"); - return buildReport; + Debug.Log("Build finished"); + return buildReport; } #endif [Serializable] public struct BuildInfo { - public string buildPath; - public bool isDebug; + public string BuildPath; + public bool IsDebug; } public static BuildInfo ReadBuildInfo() diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs index be034a74ee..d3a343f0a4 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/CustomPrefabSpawnerForPerformanceTests.cs @@ -1,6 +1,4 @@ using System; -using System.Collections; -using System.Collections.Generic; using UnityEngine; using MLAPI.Spawning; @@ -15,7 +13,7 @@ public class CustomPrefabSpawnerForPerformanceTests : INetworkPrefabInstanceH public CustomPrefabSpawnerForPerformanceTests(T prefabToSpawn, int maxObjectsToSpawn, Action setupSpawnedObject, Action onRelease) { m_ObjectPool = new GameObjectPool(); - m_ObjectPool.Init(maxObjectsToSpawn, prefabToSpawn); + m_ObjectPool.Initialize(maxObjectsToSpawn, prefabToSpawn); m_SetupSpawnedObject = setupSpawnedObject; m_OnRelease = onRelease; } diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs index 289ce619b0..5777b40ec5 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/GameObjectPool.cs @@ -1,6 +1,5 @@ using System; using System.Collections.Generic; -using UnityEngine; using Object = UnityEngine.Object; namespace MLAPI.MultiprocessRuntimeTests @@ -15,7 +14,7 @@ public class GameObjectPool : IDisposable where T : NetworkBehaviour private Stack m_FreeIndexes; private Dictionary m_ReverseLookup = new Dictionary(); - public void Init(int originalCount, T prefabToSpawn) + public void Initialize(int originalCount, T prefabToSpawn) { m_AllGameObject = new List(originalCount); m_FreeIndexes = new Stack(originalCount); diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index 50d90d1a73..076283606a 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -21,7 +21,7 @@ public static void StartWorkerNode() try { - var buildPath = BuildMultiprocessTestPlayer.ReadBuildInfo().buildPath; + var buildPath = BuildMultiprocessTestPlayer.ReadBuildInfo().BuildPath; switch (Application.platform) { case RuntimePlatform.OSXPlayer: diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index ae4d2568f8..d61813fd23 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -25,21 +25,21 @@ public class NetworkVariablePerformanceTests : BaseMultiprocessTests private class OneNetVar : NetworkBehaviour { - public static int nbInstances; - public NetworkVariableInt oneInt = new NetworkVariableInt(); + public static int InstanceCount; + public NetworkVariableInt OneInt = new NetworkVariableInt(); - public void Init() + public void Initialize() { - nbInstances++; + InstanceCount++; if (IsServer) { - oneInt.Value = 1; + OneInt.Value = 1; } } public static void Stop() { - nbInstances--; + InstanceCount--; } } @@ -54,14 +54,14 @@ private void OnSceneLoadedInitSetupSuite(Scene scene, LoadSceneMode loadSceneMod { InitPrefab(); s_ServerObjectPool = new GameObjectPool(); - s_ServerObjectPool.Init(k_MaxObjectsToSpawn, m_PrefabToSpawn); + s_ServerObjectPool.Initialize(k_MaxObjectsToSpawn, m_PrefabToSpawn); } private void InitPrefab() { if (m_PrefabToSpawn == null) { - var prefabCopy = Object.Instantiate(PrefabReference.Instance.referencedPrefab); + var prefabCopy = Object.Instantiate(PrefabReference.Instance.ReferencedPrefab); m_PrefabToSpawn = prefabCopy.AddComponent(); } } @@ -69,9 +69,9 @@ private void InitPrefab() [UnityTest, Performance, MultiprocessContextBasedTest] public IEnumerator TestSpawningManyObjects([Values(1, 2, 1000, 2000, 10000)] int nbObjects) { - InitContextSteps(); + InitializeContextSteps(); - if (!isRegistering && TestCoordinator.Instance.NetworkManager.IsServer && BuildMultiprocessTestPlayer.ReadBuildInfo().isDebug) + if (!IsRegistering && TestCoordinator.Instance.NetworkManager.IsServer && BuildMultiprocessTestPlayer.ReadBuildInfo().IsDebug) { // build test player in debug mode to enable this var timeToWait = 20; @@ -97,7 +97,7 @@ public IEnumerator TestSpawningManyObjects([Values(1, 2, 1000, 2000, 10000)] int // add client side reporter for later spawn steps void Update(float deltaTime) { - var count = OneNetVar.nbInstances; + var count = OneNetVar.InstanceCount; if (count > 0) { TestCoordinator.Instance.WriteTestResultsServerRpc(count); @@ -121,7 +121,7 @@ void Update(float deltaTime) // spawn prefabs for test var totalAllocSampleGroup = new SampleGroup("GC Alloc", SampleUnit.Kilobyte); var beforeAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(); - Measure.Custom(totalAllocSampleGroup, beforeAllocatedMemory / 1024); + Measure.Custom(totalAllocSampleGroup, beforeAllocatedMemory / 1024f); for (int i = 0; i < nbObjects; i++) { var spawnedObject = s_ServerObjectPool.Get(); @@ -131,10 +131,9 @@ void Update(float deltaTime) } var afterAllocatedMemory = Profiler.GetTotalAllocatedMemoryLong(); - Measure.Custom(totalAllocSampleGroup, afterAllocatedMemory / 1024); + Measure.Custom(totalAllocSampleGroup, afterAllocatedMemory / 1024f); var diffAllocSampleGroup = new SampleGroup("GC Alloc diff for Spawn Server side", SampleUnit.Byte); Measure.Custom(diffAllocSampleGroup, afterAllocatedMemory - beforeAllocatedMemory); - } }, additionalIsFinishedWaiter: () => { @@ -156,7 +155,8 @@ void Update(float deltaTime) return finishedCount == WorkerCount; }); - float serverLastResult = 0; + + var serverLastResult = 0f; yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { // add measurements @@ -175,24 +175,22 @@ void Update(float deltaTime) Measure.Custom(allocated, result); serverLastResult = result; } - }); yield return new ExecuteStepInContext(StepExecutionContext.Clients, nbObjectsBytes => { var nbObjectsParam = BitConverter.ToInt32(nbObjectsBytes, 0); - Assert.That(GameObject.FindObjectsOfType(typeof(OneNetVar)).Length, Is.EqualTo(nbObjectsParam + 1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn + Assert.That(Object.FindObjectsOfType(typeof(OneNetVar)).Length, Is.EqualTo(nbObjectsParam + 1), "Wrong number of spawned objects client side"); // +1 for the prefab to spawn }, paramToPass: BitConverter.GetBytes(nbObjects)); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { Debug.Log($"finished with test for {nbObjects} expected objects and got {serverLastResult} objects"); }); - } [UnityTearDown, MultiprocessContextBasedTest] public IEnumerator UnityTeardown() { - InitContextSteps(); + InitializeContextSteps(); yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { @@ -212,7 +210,7 @@ public IEnumerator UnityTeardown() void UpdateWaitForAllOneNetVarToDespawn(float deltaTime) { - if (OneNetVar.nbInstances == 0) + if (OneNetVar.InstanceCount == 0) { NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateWaitForAllOneNetVarToDespawn; TestCoordinator.Instance.ClientFinishedServerRpc(); @@ -239,7 +237,7 @@ public override void TeardownSuite() private static void SetupSpawnedObject(OneNetVar spawnedObject) { - spawnedObject.Init(); + spawnedObject.Initialize(); } private static void StopSpawnedObject(OneNetVar destroyedObject) From be40cfda5610c59fde38433574afe6207fa2fbfb Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:22:51 -0400 Subject: [PATCH 59/66] # --- testproject/Assets/Scenes/MultiprocessTestScene.unity | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/testproject/Assets/Scenes/MultiprocessTestScene.unity b/testproject/Assets/Scenes/MultiprocessTestScene.unity index d4a6e985ce..04b51e1819 100644 --- a/testproject/Assets/Scenes/MultiprocessTestScene.unity +++ b/testproject/Assets/Scenes/MultiprocessTestScene.unity @@ -743,7 +743,7 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: 068bf11ceb1344667af4cc40950f44f4, type: 3} m_Name: m_EditorClassIdentifier: - referencedPrefab: {fileID: 5637023994061915634, guid: b0952a471c5a147cb92f6afcdb648f8a, + ReferencedPrefab: {fileID: 5637023994061915634, guid: b0952a471c5a147cb92f6afcdb648f8a, type: 3} --- !u!114 &1211923378 MonoBehaviour: @@ -817,8 +817,6 @@ MonoBehaviour: m_Script: {fileID: 11500000, guid: ef1240e0784f84eadb77fe822e2e03c7, type: 3} m_Name: m_EditorClassIdentifier: - isRegistering: 0 - hasRegistered: 0 --- !u!1 &1674777071 GameObject: m_ObjectHideFlags: 0 From 750a3b52f99c0e92d915e2b7dfaa8939b6ec459b Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:23:11 -0400 Subject: [PATCH 60/66] # --- .../Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs index 33396b0ec6..69d83f358f 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/TestCoordinator.cs @@ -250,7 +250,7 @@ public void TriggerActionIdClientRpc(string actionId, byte[] args, ClientRpcPara Debug.Log($"received RPC from server, client side triggering action ID {actionId}"); try { - ExecuteStepInContext.allActions[actionID].Invoke(args); + ExecuteStepInContext.AllActions[actionId].Invoke(args); } catch (Exception e) { From 0d696117eb0a23fc3f43dfdc7d3d556a717435f2 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:36:08 -0400 Subject: [PATCH 61/66] fix --- .../Runtime/MultiprocessRuntime/readme.md | 28 ++++++++++++------- 1 file changed, 18 insertions(+), 10 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index e1a9ee218d..7c880cc2c4 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -13,19 +13,19 @@ There's a few steps to write a multiprocess test 1. Your test class needs to inherit from `BaseMultiprocessTests` 2. Each test method needs the `MultiprocessContextBasedTest` attribute -3. Each test method needs to run `InitContextSteps();` +3. Each test method needs to run `InitializeContextSteps();` 4. Each context based step can use -```C# +```cs yield return new ExecuteStepInContext(StepExecutionContext.Clients, stepToExecute: nbObjectsBytes => { // Something here }); ``` A test method would look like -```C# +```cs [UnityTest, MultiprocessContextBasedTest] public IEnumerator MyTest() { - InitContextSteps(); // the only call that should be made outside of context based tests + InitializeContextSteps(); // the only call that should be made outside of context based tests yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => { Debug.Log("server stuff"); @@ -41,7 +41,7 @@ A test method would look like Your test code shouldn't execute outside of these steps (as that test method can be executed multiple times, once for step registration and once for the actual test run for example) Another way to write a multiprocess test without context based steps is to use TestCoordinator directly. -```C# +```cs private static void ExecuteSimpleCoordinatorTest() { TestCoordinator.Instance.WriteTestResultsServerRpc(float.PositiveInfinity); @@ -68,21 +68,28 @@ Another way to write a multiprocess test without context based steps is to use T Here's a complete set of examples using the API -```C# -using //... +```cs +using System; +using System.Collections; +using System.Collections.Generic; +using NUnit.Framework; +using Unity.PerformanceTesting; +using UnityEngine; +using UnityEngine.Profiling; +using UnityEngine.TestTools; using static ExecuteStepInContext; namespace MLAPI.MultiprocessRuntimeTests { public class DemoProcessTest : BaseMultiprocessTests { - protected override int NbWorkers { get; } = 2; // spawns 2 clients connecting to the test runner + protected override int WorkerCount { get; } = 2; // spawns 2 clients connecting to the test runner protected override bool m_IsPerformanceTest { get; } = false; // specifies whether this should execute from editor or not [UnityTest, MultiprocessContextBasedTest] // attribute necessary for context based step execution public IEnumerator MyTest() { - InitContextSteps(); // necessary to initialize context based steps + InitializeContextSteps(); // necessary to initialize context based steps // These steps execute sequentially. yield return new ExecuteStepInContext(StepExecutionContext.Server, bytes => @@ -138,7 +145,7 @@ namespace MLAPI.MultiprocessRuntimeTests } NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; }, waitMultipleUpdates: true); // this keeps waiting "are you done? are you done? are you done?" and relies on the clients calling the "ClientFinishedServerRpc" - + yield return new ExecuteStepInContext(StepExecutionContext.Clients, bytes => { int cpt = 0; @@ -169,6 +176,7 @@ namespace MLAPI.MultiprocessRuntimeTests } } + ``` From 54f4c09ab78cbb8153190014a5d92c4e26a48efb Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:38:49 -0400 Subject: [PATCH 62/66] # --- .../Assets/Tests/Runtime/MultiprocessRuntime/readme.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md index 7c880cc2c4..983ea06c28 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/readme.md @@ -53,16 +53,17 @@ Another way to write a multiprocess test without context based steps is to use T // Call the client side method TestCoordinator.Instance.InvokeFromMethodActionRpc(ExecuteSimpleCoordinatorTest); - var nbResults = 0; - for (int i = 0; i < NbWorkers; i++) // wait and test for the two clients + var resultCount = 0; + for (int i = 0; i < WorkerCount; i++) // wait and test for the two clients { yield return new WaitUntil(TestCoordinator.ResultIsSet()); var (clientId, result) = TestCoordinator.ConsumeCurrentResult().Take(1).Single(); Assert.Greater(result, 0f); - nbResults++; + resultCount++; } - Assert.That(nbResults, Is.EqualTo(NbWorkers)); + + Assert.That(resultCount, Is.EqualTo(WorkerCount)); } ``` From 424ee6deb7590cd72ab6f5cb9961e18061e7bd73 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Thu, 8 Jul 2021 23:53:11 -0400 Subject: [PATCH 63/66] invalid --- .../MultiprocessRuntime/NetworkVariablePerformanceTests.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index d61813fd23..d798043500 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -15,6 +15,7 @@ namespace MLAPI.MultiprocessRuntimeTests { public class NetworkVariablePerformanceTests : BaseMultiprocessTests { + public int somethingInvalid = 1; protected override int WorkerCount { get; } = 1; private const int k_MaxObjectsToSpawn = 10000; private List m_ServerSpawnedObjects = new List(); From 50398b393a2f51ca447b52b7ae3453bfe1b16ffd Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Fri, 9 Jul 2021 15:22:32 -0400 Subject: [PATCH 64/66] fix for automation fail right now --- .../Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs | 2 +- .../Helpers/MultiprocessOrchestration.cs | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs index ace3f408dc..f6b82d71db 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/BaseMultiprocessTests.cs @@ -18,7 +18,7 @@ public abstract class BaseMultiprocessTests { protected virtual bool m_IsPerformanceTest => true; - private bool ShouldIgnoreTests => m_IsPerformanceTest && Application.isEditor; + private bool ShouldIgnoreTests => m_IsPerformanceTest && Application.isEditor || MultiprocessOrchestration.IsUsingUTR(); // todo remove UTR check once we have proper automation /// /// Implement this to specify the amount of workers to spawn from your main test runner diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs index b09ee09914..6dec09b4cb 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/Helpers/MultiprocessOrchestration.cs @@ -2,6 +2,7 @@ using System.ComponentModel; using System.Diagnostics; using System.IO; +using System.Linq; using UnityEngine; using Debug = UnityEngine.Debug; @@ -60,4 +61,10 @@ public static void StartWorkerNode() throw; } } + + // todo remove this once we have proper automation + public static bool IsUsingUTR() + { + return Environment.GetCommandLineArgs().Contains("-automated"); + } } From 5de07e4298c4d5fbcc1351cb5bc544eb5dc7c24f Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 12 Jul 2021 11:45:14 -0400 Subject: [PATCH 65/66] fixing package version issues --- .../NetworkVariablePerformanceTests.cs | 18 +++++++++--------- testproject/Packages/manifest.json | 4 ++-- testproject/Packages/packages-lock.json | 8 +++----- 3 files changed, 14 insertions(+), 16 deletions(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index d61813fd23..74def8e28b 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -52,12 +52,12 @@ public override void SetupTestSuite() private void OnSceneLoadedInitSetupSuite(Scene scene, LoadSceneMode loadSceneMode) { - InitPrefab(); + InitializePrefab(); s_ServerObjectPool = new GameObjectPool(); s_ServerObjectPool.Initialize(k_MaxObjectsToSpawn, m_PrefabToSpawn); } - private void InitPrefab() + private void InitializePrefab() { if (m_PrefabToSpawn == null) { @@ -87,7 +87,7 @@ public IEnumerator TestSpawningManyObjects([Values(1, 2, 1000, 2000, 10000)] int yield return new ExecuteStepInContext(StepExecutionContext.Clients, stepToExecute: nbObjectsBytes => { // setup clients - InitPrefab(); + InitializePrefab(); var targetCount = BitConverter.ToInt32(nbObjectsBytes, 0); m_ClientPrefabHandler = new CustomPrefabSpawnerForPerformanceTests(m_PrefabToSpawn, k_MaxObjectsToSpawn, SetupSpawnedObject, StopSpawnedObject); @@ -95,7 +95,7 @@ public IEnumerator TestSpawningManyObjects([Values(1, 2, 1000, 2000, 10000)] int Assert.That(hasAddedHandler); // add client side reporter for later spawn steps - void Update(float deltaTime) + void UpdateFunc(float deltaTime) { var count = OneNetVar.InstanceCount; if (count > 0) @@ -105,12 +105,12 @@ void Update(float deltaTime) if (count >= targetCount) { // we got what we want, don't update results any longer - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateFunc; } } } - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += Update; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateFunc; }, paramToPass: BitConverter.GetBytes(nbObjects)); yield return new ExecuteStepInContext(StepExecutionContext.Server, _ => @@ -208,16 +208,16 @@ public IEnumerator UnityTeardown() { NetworkManager.Singleton.gameObject.GetComponent().OnUpdate = null; // todo move access to callbackcomponent to singleton - void UpdateWaitForAllOneNetVarToDespawn(float deltaTime) + void UpdateWaitForAllOneNetVarToDespawnFunc(float deltaTime) { if (OneNetVar.InstanceCount == 0) { - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateWaitForAllOneNetVarToDespawn; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate -= UpdateWaitForAllOneNetVarToDespawnFunc; TestCoordinator.Instance.ClientFinishedServerRpc(); } } - NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateWaitForAllOneNetVarToDespawn; + NetworkManager.Singleton.gameObject.GetComponent().OnUpdate += UpdateWaitForAllOneNetVarToDespawnFunc; }, waitMultipleUpdates: true, ignoreTimeoutException: true); // ignoring timeout since you don't want to hide any issues in the main tests yield return new ExecuteStepInContext(StepExecutionContext.Clients, _ => diff --git a/testproject/Packages/manifest.json b/testproject/Packages/manifest.json index 824664ed79..4bc3118fdb 100644 --- a/testproject/Packages/manifest.json +++ b/testproject/Packages/manifest.json @@ -1,7 +1,7 @@ { "dependencies": { "com.unity.collab-proxy": "1.5.7", - "com.unity.ide.rider": "3.0.6", + "com.unity.ide.rider": "3.0.5", "com.unity.ide.visualstudio": "2.0.8", "com.unity.ide.vscode": "1.2.3", "com.unity.multiplayer.mlapi": "file:../../com.unity.multiplayer.mlapi", @@ -10,7 +10,7 @@ "com.unity.test-framework": "1.1.27", "com.unity.test-framework.performance": "2.3.1-preview", "com.unity.textmeshpro": "3.0.6", - "com.unity.timeline": "1.5.4", + "com.unity.timeline": "1.5.2", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", diff --git a/testproject/Packages/packages-lock.json b/testproject/Packages/packages-lock.json index f82742c76d..bc2860bfca 100644 --- a/testproject/Packages/packages-lock.json +++ b/testproject/Packages/packages-lock.json @@ -36,12 +36,10 @@ "url": "https://packages.unity.com" }, "com.unity.ide.rider": { - "version": "3.0.6", + "version": "3.0.5", "depth": 0, "source": "registry", - "dependencies": { - "com.unity.ext.nunit": "1.0.6" - }, + "dependencies": {}, "url": "https://packages.unity.com" }, "com.unity.ide.visualstudio": { @@ -151,7 +149,7 @@ "url": "https://packages.unity.com" }, "com.unity.timeline": { - "version": "1.5.4", + "version": "1.5.2", "depth": 0, "source": "registry", "dependencies": { From ef87b7ac78f84ef9776cacac427358a673995b57 Mon Sep 17 00:00:00 2001 From: Samuel Bellomo Date: Mon, 12 Jul 2021 19:53:42 -0400 Subject: [PATCH 66/66] not sure why this was there... --- .../MultiprocessRuntime/NetworkVariablePerformanceTests.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs index c65adc42a0..74def8e28b 100644 --- a/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs +++ b/testproject/Assets/Tests/Runtime/MultiprocessRuntime/NetworkVariablePerformanceTests.cs @@ -15,7 +15,6 @@ namespace MLAPI.MultiprocessRuntimeTests { public class NetworkVariablePerformanceTests : BaseMultiprocessTests { - public int somethingInvalid = 1; protected override int WorkerCount { get; } = 1; private const int k_MaxObjectsToSpawn = 10000; private List m_ServerSpawnedObjects = new List();