From bd7f75f50c2c593bf711ffd56749289871182b1b Mon Sep 17 00:00:00 2001 From: MrGadget <9826063+MrGadget1024@users.noreply.github.com> Date: Fri, 28 Jan 2022 21:46:10 -0500 Subject: [PATCH] Added Tests --- Assets/Mirror/Tests.meta | 8 + Assets/Mirror/Tests/Common.meta | 8 + Assets/Mirror/Tests/Common/Castle.Core.dll | Bin 0 -> 442368 bytes .../Mirror/Tests/Common/Castle.Core.dll.meta | 112 + .../Tests/Common/ClientSceneTestsBase.cs | 62 + .../Tests/Common/ClientSceneTestsBase.cs.meta | 11 + .../ClientSceneTests_RegisterPrefabBase.cs | 212 + ...lientSceneTests_RegisterPrefabBase.cs.meta | 11 + .../Tests/Common/FakeNetworkConnection.cs | 12 + .../Common/FakeNetworkConnection.cs.meta | 11 + Assets/Mirror/Tests/Common/MemoryTransport.cs | 208 + .../Tests/Common/MemoryTransport.cs.meta | 11 + .../Tests/Common/Mirror.Tests.Common.asmdef | 16 + .../Common/Mirror.Tests.Common.asmdef.meta | 7 + .../Mirror/Tests/Common/MirrorEditModeTest.cs | 14 + .../Tests/Common/MirrorEditModeTest.cs.meta | 11 + .../Mirror/Tests/Common/MirrorPlayModeTest.cs | 27 + .../Tests/Common/MirrorPlayModeTest.cs.meta | 11 + Assets/Mirror/Tests/Common/MirrorTest.cs | 557 ++ Assets/Mirror/Tests/Common/MirrorTest.cs.meta | 11 + Assets/Mirror/Tests/Common/NSubstitute.dll | Bin 0 -> 148480 bytes .../Mirror/Tests/Common/NSubstitute.dll.meta | 112 + .../System.Threading.Tasks.Extensions.dll | Bin 0 -> 25864 bytes ...System.Threading.Tasks.Extensions.dll.meta | 117 + Assets/Mirror/Tests/Editor.meta | 8 + Assets/Mirror/Tests/Editor/Batching.meta | 3 + .../Tests/Editor/Batching/BatcherTests.cs | 230 + .../Editor/Batching/BatcherTests.cs.meta | 11 + .../Tests/Editor/Batching/UnbatcherTests.cs | 138 + .../Editor/Batching/UnbatcherTests.cs.meta | 3 + .../Tests/Editor/ClientRpcOverrideTest.cs | 134 + .../Editor/ClientRpcOverrideTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/ClientRpcTest.cs | 155 + .../Mirror/Tests/Editor/ClientRpcTest.cs.meta | 11 + .../Editor/ClientSceneTests_ClearSpawners.cs | 58 + .../ClientSceneTests_ClearSpawners.cs.meta | 11 + .../Editor/ClientSceneTests_GetPrefab.cs | 62 + .../Editor/ClientSceneTests_GetPrefab.cs.meta | 11 + .../Tests/Editor/ClientSceneTests_OnSpawn.cs | 736 ++ .../Editor/ClientSceneTests_OnSpawn.cs.meta | 11 + ...ntSceneTests_PrepareToSpawnSceneObjects.cs | 104 + ...neTests_PrepareToSpawnSceneObjects.cs.meta | 11 + .../Editor/ClientSceneTests_RegisterPrefab.cs | 300 + .../ClientSceneTests_RegisterPrefab.cs.meta | 11 + .../ClientSceneTests_RegisterSpawnHandler.cs | 224 + ...entSceneTests_RegisterSpawnHandler.cs.meta | 11 + .../ClientSceneTests_UnregisterPrefab.cs | 48 + .../ClientSceneTests_UnregisterPrefab.cs.meta | 11 + ...ClientSceneTests_UnregisterSpawnHandler.cs | 33 + ...tSceneTests_UnregisterSpawnHandler.cs.meta | 11 + .../Tests/Editor/CommandOverrideTest.cs | 182 + .../Tests/Editor/CommandOverrideTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/CommandTest.cs | 247 + .../Mirror/Tests/Editor/CommandTest.cs.meta | 11 + .../Mirror/Tests/Editor/CompressionTests.cs | 257 + .../Tests/Editor/CompressionTests.cs.meta | 3 + Assets/Mirror/Tests/Editor/CustomRWTest.cs | 53 + .../Mirror/Tests/Editor/CustomRWTest.cs.meta | 11 + .../Mirror/Tests/Editor/EnumReadWriteTests.cs | 91 + .../Tests/Editor/EnumReadWriteTests.cs.meta | 11 + .../Editor/ExponentialMovingAverageTest.cs | 42 + .../ExponentialMovingAverageTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/ExtensionsTest.cs | 26 + .../Tests/Editor/ExtensionsTest.cs.meta | 3 + .../Tests/Editor/FieldsInBaseClasses.cs | 56 + .../Tests/Editor/FieldsInBaseClasses.cs.meta | 11 + Assets/Mirror/Tests/Editor/Generated.meta | 8 + .../Editor/Generated/AttritubeTest.gen.cs | 5946 +++++++++++++++++ .../Generated/AttritubeTest.gen.cs.meta | 11 + .../Generated/CollectionWriterTests.gen.cs | 1049 +++ .../CollectionWriterTests.gen.cs.meta | 11 + Assets/Mirror/Tests/Editor/Grid2DTests.cs | 58 + .../Mirror/Tests/Editor/Grid2DTests.cs.meta | 3 + .../Editor/InterestManagementTests_Common.cs | 95 + .../InterestManagementTests_Common.cs.meta | 3 + .../Editor/InterestManagementTests_Default.cs | 62 + .../InterestManagementTests_Default.cs.meta | 3 + .../InterestManagementTests_Distance.cs | 142 + .../InterestManagementTests_Distance.cs.meta | 3 + .../InterestManagementTests_SpatialHashing.cs | 154 + ...restManagementTests_SpatialHashing.cs.meta | 3 + .../Tests/Editor/LocalConnectionTest.cs | 71 + .../Tests/Editor/LocalConnectionTest.cs.meta | 11 + .../Tests/Editor/MessageInheritanceTest.cs | 125 + .../Editor/MessageInheritanceTest.cs.meta | 11 + .../Mirror/Tests/Editor/MessagePackingTest.cs | 143 + .../Tests/Editor/MessagePackingTest.cs.meta | 11 + .../Tests/Editor/MiddlewareTransportTest.cs | 386 ++ .../Editor/MiddlewareTransportTest.cs.meta | 11 + .../Mirror/Tests/Editor/Mirror.Tests.asmdef | 34 + .../Tests/Editor/Mirror.Tests.asmdef.meta | 7 + Assets/Mirror/Tests/Editor/MultiplexTest.cs | 180 + .../Mirror/Tests/Editor/MultiplexTest.cs.meta | 11 + .../Editor/NetworkBehaviourDirtyBitsTests.cs | 184 + .../NetworkBehaviourDirtyBitsTests.cs.meta | 3 + .../Editor/NetworkBehaviourSerializeTest.cs | 341 + .../NetworkBehaviourSerializeTest.cs.meta | 11 + .../Tests/Editor/NetworkBehaviourTests.cs | 884 +++ .../Editor/NetworkBehaviourTests.cs.meta | 11 + .../Mirror/Tests/Editor/NetworkClientTests.cs | 128 + .../Tests/Editor/NetworkClientTests.cs.meta | 11 + .../Editor/NetworkConnectionToClientTests.cs | 90 + .../NetworkConnectionToClientTests.cs.meta | 3 + .../NetworkIdentitySerializationTests.cs | 180 + .../NetworkIdentitySerializationTests.cs.meta | 3 + .../Tests/Editor/NetworkIdentityTests.cs | 967 +++ .../Tests/Editor/NetworkIdentityTests.cs.meta | 11 + .../Mirror/Tests/Editor/NetworkLoopTests.cs | 110 + .../Tests/Editor/NetworkLoopTests.cs.meta | 3 + ...rkManagerStopHostOnServerDisconnectTest.cs | 39 + ...agerStopHostOnServerDisconnectTest.cs.meta | 11 + .../Mirror/Tests/Editor/NetworkManagerTest.cs | 148 + .../Tests/Editor/NetworkManagerTest.cs.meta | 11 + .../Tests/Editor/NetworkMessageTests.cs | 65 + .../Tests/Editor/NetworkMessageTests.cs.meta | 11 + .../Mirror/Tests/Editor/NetworkReaderTest.cs | 84 + .../Tests/Editor/NetworkReaderTest.cs.meta | 11 + .../Tests/Editor/NetworkReaderWriterTest.cs | 31 + .../Editor/NetworkReaderWriterTest.cs.meta | 11 + .../Mirror/Tests/Editor/NetworkServerTest.cs | 1323 ++++ .../Tests/Editor/NetworkServerTest.cs.meta | 11 + .../Tests/Editor/NetworkTransform2kTests.cs | 362 + .../Editor/NetworkTransform2kTests.cs.meta | 3 + .../Editor/NetworkWriterCollectionTest.cs | 67 + .../NetworkWriterCollectionTest.cs.meta | 11 + .../Mirror/Tests/Editor/NetworkWriterTest.cs | 1389 ++++ .../Tests/Editor/NetworkWriterTest.cs.meta | 11 + .../Mirror/Tests/Editor/OverloadMethodTest.cs | 53 + .../Tests/Editor/OverloadMethodTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/PoolTests.cs | 45 + Assets/Mirror/Tests/Editor/PoolTests.cs.meta | 11 + Assets/Mirror/Tests/Editor/RemoteTestBase.cs | 15 + .../Tests/Editor/RemoteTestBase.cs.meta | 11 + .../Tests/Editor/RpcNetworkIdentityTest.cs | 114 + .../Editor/RpcNetworkIdentityTest.cs.meta | 11 + .../Editor/ScriptableObjectWriterTest.cs | 43 + .../Editor/ScriptableObjectWriterTest.cs.meta | 11 + .../Editor/SnapshotInterpolationTests.cs | 724 ++ .../Editor/SnapshotInterpolationTests.cs.meta | 11 + .../Tests/Editor/StructMessagesTests.cs | 36 + .../Tests/Editor/StructMessagesTests.cs.meta | 11 + .../Mirror/Tests/Editor/SyncDictionaryTest.cs | 353 + .../Tests/Editor/SyncDictionaryTest.cs.meta | 11 + .../Mirror/Tests/Editor/SyncListClassTest.cs | 78 + .../Tests/Editor/SyncListClassTest.cs.meta | 11 + .../Mirror/Tests/Editor/SyncListStructTest.cs | 73 + .../Tests/Editor/SyncListStructTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/SyncListTest.cs | 434 ++ .../Mirror/Tests/Editor/SyncListTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/SyncSetTest.cs | 322 + .../Mirror/Tests/Editor/SyncSetTest.cs.meta | 11 + .../Tests/Editor/SyncVarAttributeHookTest.cs | 521 ++ .../Editor/SyncVarAttributeHookTest.cs.meta | 11 + .../Tests/Editor/SyncVarAttributeTest.cs | 438 ++ .../Tests/Editor/SyncVarAttributeTest.cs.meta | 11 + .../Tests/Editor/SyncVarAttributeTestBase.cs | 37 + .../Editor/SyncVarAttributeTestBase.cs.meta | 11 + .../Tests/Editor/SyncVarGameObjectTests.cs | 169 + .../Editor/SyncVarGameObjectTests.cs.meta | 3 + .../SyncVarNetworkBehaviourAbstractTests.cs | 190 + ...ncVarNetworkBehaviourAbstractTests.cs.meta | 3 + .../SyncVarNetworkBehaviourInheritedTests.cs | 187 + ...cVarNetworkBehaviourInheritedTests.cs.meta | 3 + .../Editor/SyncVarNetworkIdentityTests.cs | 166 + .../SyncVarNetworkIdentityTests.cs.meta | 3 + Assets/Mirror/Tests/Editor/SyncVarTests.cs | 254 + .../Mirror/Tests/Editor/SyncVarTests.cs.meta | 3 + .../Tests/Editor/TargetRpcOverrideTest.cs | 138 + .../Editor/TargetRpcOverrideTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/TargetRpcTest.cs | 181 + .../Mirror/Tests/Editor/TargetRpcTest.cs.meta | 11 + Assets/Mirror/Tests/Editor/TestPrefabs.meta | 8 + .../PrefabWithChildrenForClientScene.prefab | 97 + ...efabWithChildrenForClientScene.prefab.meta | 7 + .../ValidPrefabForClientScene.prefab | 49 + .../ValidPrefabForClientScene.prefab.meta | 7 + .../invalidPrefabForClientScene.prefab | 32 + .../invalidPrefabForClientScene.prefab.meta | 7 + Assets/Mirror/Tests/Editor/UtilsTests.cs | 31 + Assets/Mirror/Tests/Editor/UtilsTests.cs.meta | 11 + Assets/Mirror/Tests/Editor/Weaver.meta | 8 + .../Tests/Editor/Weaver/.WeaverTests.csproj | 280 + .../Tests/Editor/Weaver/ExtraAssembly.meta | 8 + .../Weaver/ExtraAssembly/ComplexClass.cs | 14 + .../Weaver/ExtraAssembly/ComplexClass.cs.meta | 11 + .../Editor/Weaver/ExtraAssembly/README.md | 3 + .../Weaver/ExtraAssembly/README.md.meta | 7 + .../Editor/Weaver/ExtraAssembly/SomeData.cs | 7 + .../Weaver/ExtraAssembly/SomeData.cs.meta | 11 + .../Weaver/ExtraAssembly/SomeDataClass.cs | 7 + .../ExtraAssembly/SomeDataClass.cs.meta | 11 + .../SomeDataClassWithConstructor.cs | 12 + .../SomeDataClassWithConstructor.cs.meta | 11 + .../ExtraAssembly/SomeDataWithWriter.cs | 19 + .../ExtraAssembly/SomeDataWithWriter.cs.meta | 11 + .../WeaverTestExtraAssembly.asmdef | 16 + .../WeaverTestExtraAssembly.asmdef.meta | 7 + Assets/Mirror/Tests/Editor/Weaver/README.md | 15 + .../Mirror/Tests/Editor/Weaver/README.md.meta | 3 + .../Tests/Editor/Weaver/WeaverAssembler.cs | 180 + .../Editor/Weaver/WeaverAssembler.cs.meta | 11 + .../Editor/Weaver/WeaverClientRpcTests.cs | 28 + .../Weaver/WeaverClientRpcTests.cs.meta | 11 + .../WeaverClientRpcTests_IsSuccess.meta | 3 + .../BehaviourCanBeSentInRpc.cs | 13 + .../BehaviourCanBeSentInRpc.cs.meta | 11 + .../ClientRpcThatExcludesOwner.cs | 13 + .../ClientRpcThatExcludesOwner.cs.meta | 11 + .../ClientRpcValid.cs | 10 + .../ClientRpcValid.cs.meta | 11 + .../OverrideVirtualClientRpc.cs | 23 + .../OverrideVirtualClientRpc.cs.meta | 11 + .../VirtualClientRpc.cs | 13 + .../VirtualClientRpc.cs.meta | 11 + .../AbstractClientRpc.cs | 10 + .../ClientRpcCantBeStatic.cs | 10 + .../OverrideAbstractClientRpc.cs | 20 + .../WeaverClientServerAttributeTests.cs | 130 + .../WeaverClientServerAttributeTests.cs.meta | 11 + .../ClientAttributeOnAbstractMethod.cs | 10 + .../ClientAttributeOnOverrideMethod.cs | 21 + .../ClientAttributeOnVirutalMethod.cs | 13 + .../MonoBehaviourClient.cs | 11 + .../MonoBehaviourServer.cs | 11 + .../NetworkBehaviourClient.cs | 13 + .../NetworkBehaviourServer.cs | 13 + .../RegularClassClient.cs | 10 + .../RegularClassServer.cs | 10 + .../ServerAttributeOnAbstractMethod.cs | 10 + .../ServerAttributeOnOverrideMethod.cs | 21 + .../ServerAttributeOnVirutalMethod.cs | 13 + .../StaticClassClient.cs | 10 + .../StaticClassServer.cs | 10 + .../Tests/Editor/Weaver/WeaverCommandTests.cs | 42 + .../Editor/Weaver/WeaverCommandTests.cs.meta | 11 + .../Weaver/WeaverCommandTests_IsSuccess.meta | 3 + .../CommandThatIgnoresAuthority.cs | 13 + .../CommandThatIgnoresAuthority.cs.meta | 11 + ...hatIgnoresAuthorityWithSenderConnection.cs | 13 + ...noresAuthorityWithSenderConnection.cs.meta | 11 + .../CommandValid.cs | 10 + .../CommandValid.cs.meta | 11 + .../CommandWithArguments.cs | 13 + .../CommandWithArguments.cs.meta | 11 + ...CommandWithSenderConnectionAndOtherArgs.cs | 13 + ...ndWithSenderConnectionAndOtherArgs.cs.meta | 11 + .../OverrideVirtualCallBaseCommand.cs | 23 + .../OverrideVirtualCallBaseCommand.cs.meta | 11 + ...CallsBaseCommandWithMultipleBaseClasses.cs | 27 + ...BaseCommandWithMultipleBaseClasses.cs.meta | 11 + ...rideVirtualCallsBaseCommandWithOverride.cs | 34 + ...irtualCallsBaseCommandWithOverride.cs.meta | 11 + .../OverrideVirtualCommand.cs | 22 + .../OverrideVirtualCommand.cs.meta | 11 + .../VirtualCommand.cs | 14 + .../VirtualCommand.cs.meta | 11 + .../WeaverCommandTests~/AbstractCommand.cs | 11 + .../CommandCantBeStatic.cs | 10 + ...workConnectionThatIsNotSenderConnection.cs | 14 + ...workConnectionThatIsNotSenderConnection.cs | 13 + .../OverrideAbstractCommand.cs | 20 + .../Weaver/WeaverGeneralTests_IsSuccess.meta | 3 + .../RecursionCount.cs | 22 + .../RecursionCount.cs.meta | 11 + ...stingScriptableObjectArraySerialization.cs | 40 + ...ScriptableObjectArraySerialization.cs.meta | 11 + ...rWriterAnotherAssemblyTests_IsSuccess.meta | 8 + ...eadWriteForTypesFromDifferentAssemblies.cs | 14 + ...iteForTypesFromDifferentAssemblies.cs.meta | 11 + .../CreatesForClassFromDifferentAssemblies.cs | 14 + ...tesForClassFromDifferentAssemblies.cs.meta | 11 + ...DifferentAssembliesWithValidConstructor.cs | 14 + ...rentAssembliesWithValidConstructor.cs.meta | 11 + ...esForComplexTypeFromDifferentAssemblies.cs | 14 + ...ComplexTypeFromDifferentAssemblies.cs.meta | 11 + ...CreatesForStructFromDifferentAssemblies.cs | 14 + ...esForStructFromDifferentAssemblies.cs.meta | 11 + ...eatesForTypeThatUsesDifferentAssemblies.cs | 19 + ...ForTypeThatUsesDifferentAssemblies.cs.meta | 11 + .../WeaverGeneratedReaderWriterTests.cs | 133 + .../WeaverGeneratedReaderWriterTests.cs.meta | 11 + ...rGeneratedReaderWriterTests_IsSuccess.meta | 3 + .../CanUseCustomReadWriteForAbstractClass.cs | 62 + ...UseCustomReadWriteForAbstractClass.cs.meta | 11 + .../CanUseCustomReadWriteForInterfaces.cs | 44 + ...CanUseCustomReadWriteForInterfaces.cs.meta | 11 + .../CreatesForArraySegment.cs | 14 + .../CreatesForArraySegment.cs.meta | 11 + .../CreatesForClass.cs | 18 + .../CreatesForClass.cs.meta | 11 + .../CreatesForClassInherited.cs | 23 + .../CreatesForClassInherited.cs.meta | 11 + .../CreatesForClassWithValidConstructor.cs | 23 + ...reatesForClassWithValidConstructor.cs.meta | 11 + .../CreatesForEnums.cs | 21 + .../CreatesForEnums.cs.meta | 11 + ...CreatesForInheritedFromScriptableObject.cs | 19 + ...esForInheritedFromScriptableObject.cs.meta | 11 + .../CreatesForList.cs | 14 + .../CreatesForList.cs.meta | 11 + .../CreatesForStructArraySegment.cs | 21 + .../CreatesForStructArraySegment.cs.meta | 11 + .../CreatesForStructList.cs | 22 + .../CreatesForStructList.cs.meta | 11 + .../CreatesForStructs.cs | 18 + .../CreatesForStructs.cs.meta | 11 + .../ExcludesNonSerializedFields.cs | 20 + .../ExcludesNonSerializedFields.cs.meta | 11 + .../GivesErrorForJaggedArray.cs | 13 + .../GivesErrorForJaggedArray.cs.meta | 11 + ...ivesErrorForClassWithNoValidConstructor.cs | 23 + .../GivesErrorForInvalidArraySegmentType.cs | 15 + .../GivesErrorForInvalidArrayType.cs | 14 + .../GivesErrorForInvalidListType.cs | 15 + .../GivesErrorForMultidimensionalArray.cs | 13 + .../GivesErrorWhenUsingAbstractClass.cs | 19 + .../GivesErrorWhenUsingInterface.cs | 15 + .../GivesErrorWhenUsingMonoBehaviour.cs | 14 + .../GivesErrorWhenUsingObject.cs | 14 + .../GivesErrorWhenUsingScriptableObject.cs | 14 + ...WhenUsingTypeInheritedFromMonoBehaviour.cs | 19 + .../GivesErrorWhenUsingUnityAsset.cs | 14 + ...gWhenRegisteringExistingExtensionMethod.cs | 32 + .../Tests/Editor/Weaver/WeaverMessageTests.cs | 30 + .../Editor/Weaver/WeaverMessageTests.cs.meta | 11 + .../Weaver/WeaverMessageTests_IsSuccess.meta | 3 + .../AbstractMessageMethods.cs | 20 + .../AbstractMessageMethods.cs.meta | 11 + .../MessageNestedInheritance.cs | 17 + .../MessageNestedInheritance.cs.meta | 11 + .../MessageSelfReferencing.cs | 16 + .../MessageSelfReferencing.cs.meta | 11 + .../MessageValid.cs | 15 + .../MessageValid.cs.meta | 11 + .../MessageWithBaseClass.cs | 20 + .../MessageWithBaseClass.cs.meta | 11 + .../MessageMemberGeneric.cs | 18 + .../MessageMemberInterface.cs | 18 + .../Editor/Weaver/WeaverMonoBehaviourTests.cs | 42 + .../Weaver/WeaverMonoBehaviourTests.cs.meta | 11 + .../WeaverMonoBehaviourTests_IsSuccess.meta | 3 + .../MonoBehaviourClient.cs | 11 + .../MonoBehaviourClient.cs.meta | 11 + .../MonoBehaviourClientCallback.cs | 11 + .../MonoBehaviourClientCallback.cs.meta | 11 + .../MonoBehaviourServer.cs | 11 + .../MonoBehaviourServer.cs.meta | 11 + .../MonoBehaviourServerCallback.cs | 11 + .../MonoBehaviourServerCallback.cs.meta | 11 + .../MonoBehaviourValid.cs | 11 + .../MonoBehaviourValid.cs.meta | 11 + .../MonoBehaviourClientRpc.cs | 11 + .../MonoBehaviourCommand.cs | 11 + .../MonoBehaviourSyncList.cs | 10 + .../MonoBehaviourSyncVar.cs | 11 + .../MonoBehaviourTargetRpc.cs | 11 + .../Weaver/WeaverNetworkBehaviourTests.cs | 243 + .../WeaverNetworkBehaviourTests.cs.meta | 11 + .../WeaverNetworkBehaviourTests_IsValid.meta | 3 + .../NetworkBehaviourAbstractBaseValid.cs | 18 + .../NetworkBehaviourAbstractBaseValid.cs.meta | 11 + .../NetworkBehaviourClientRpcDuplicateName.cs | 14 + ...orkBehaviourClientRpcDuplicateName.cs.meta | 11 + .../NetworkBehaviourTargetRpcDuplicateName.cs | 14 + ...orkBehaviourTargetRpcDuplicateName.cs.meta | 11 + ...ehaviourTargetRpcParamNetworkConnection.cs | 10 + ...ourTargetRpcParamNetworkConnection.cs.meta | 11 + .../NetworkBehaviourValid.cs | 10 + .../NetworkBehaviourValid.cs.meta | 11 + .../NetworkBehaviourClientRpcCoroutine.cs | 14 + .../NetworkBehaviourClientRpcGenericParam.cs | 10 + .../NetworkBehaviourClientRpcParamAbstract.cs | 15 + ...NetworkBehaviourClientRpcParamComponent.cs | 15 + ...ehaviourClientRpcParamNetworkConnection.cs | 10 + .../NetworkBehaviourClientRpcParamOptional.cs | 10 + .../NetworkBehaviourClientRpcParamOut.cs | 13 + .../NetworkBehaviourClientRpcParamRef.cs | 10 + .../NetworkBehaviourClientRpcVoidReturn.cs | 13 + .../NetworkBehaviourCmdCoroutine.cs | 14 + .../NetworkBehaviourCmdDuplicateName.cs | 14 + .../NetworkBehaviourCmdGenericParam.cs | 10 + .../NetworkBehaviourCmdParamAbstract.cs | 15 + .../NetworkBehaviourCmdParamComponent.cs | 15 + ...tworkBehaviourCmdParamNetworkConnection.cs | 10 + .../NetworkBehaviourCmdParamOptional.cs | 10 + .../NetworkBehaviourCmdParamOut.cs | 13 + .../NetworkBehaviourCmdParamRef.cs | 10 + .../NetworkBehaviourCmdVoidReturn.cs | 13 + .../NetworkBehaviourGeneric.cs | 9 + .../NetworkBehaviourTargetRpcCoroutine.cs | 14 + .../NetworkBehaviourTargetRpcGenericParam.cs | 10 + .../NetworkBehaviourTargetRpcParamAbstract.cs | 15 + ...NetworkBehaviourTargetRpcParamComponent.cs | 15 + ...TargetRpcParamNetworkConnectionNotFirst.cs | 10 + .../NetworkBehaviourTargetRpcParamOptional.cs | 10 + .../NetworkBehaviourTargetRpcParamOut.cs | 13 + .../NetworkBehaviourTargetRpcParamRef.cs | 10 + .../NetworkBehaviourTargetRpcVoidReturn.cs | 13 + .../Weaver/WeaverSyncDictionaryTests.cs | 26 + .../Weaver/WeaverSyncDictionaryTests.cs.meta | 11 + .../WeaverSyncDictionaryTests_IsSuccess.meta | 3 + .../GenericSyncDictionaryCanBeUsed.cs | 11 + .../GenericSyncDictionaryCanBeUsed.cs.meta | 11 + .../SyncDictionary.cs | 13 + .../SyncDictionary.cs.meta | 11 + ...yncDictionaryGenericAbstractInheritance.cs | 13 + ...ctionaryGenericAbstractInheritance.cs.meta | 11 + .../SyncDictionaryGenericInheritance.cs | 13 + .../SyncDictionaryGenericInheritance.cs.meta | 11 + ...onaryGenericStructItemWithCustomMethods.cs | 27 + ...GenericStructItemWithCustomMethods.cs.meta | 11 + ...ionaryGenericStructKeyWithCustomMethods.cs | 27 + ...yGenericStructKeyWithCustomMethods.cs.meta | 11 + .../SyncDictionaryInheritance.cs | 17 + .../SyncDictionaryInheritance.cs.meta | 11 + .../SyncDictionaryStructItem.cs | 17 + .../SyncDictionaryStructItem.cs.meta | 11 + .../SyncDictionaryStructKey.cs | 17 + .../SyncDictionaryStructKey.cs.meta | 11 + ...SyncDictionaryErrorForGenericStructItem.cs | 17 + .../SyncDictionaryErrorForGenericStructKey.cs | 16 + .../Editor/Weaver/WeaverSyncListTests.cs | 65 + .../Editor/Weaver/WeaverSyncListTests.cs.meta | 11 + .../Weaver/WeaverSyncListTests_IsSuccess.meta | 3 + .../GenericSyncListCanBeUsed.cs | 12 + .../GenericSyncListCanBeUsed.cs.meta | 11 + .../WeaverSyncListTests_IsSuccess/SyncList.cs | 9 + .../SyncList.cs.meta | 11 + .../SyncListByteValid.cs | 11 + .../SyncListByteValid.cs.meta | 11 + .../SyncListGenericAbstractInheritance.cs | 14 + ...SyncListGenericAbstractInheritance.cs.meta | 11 + .../SyncListGenericInheritance.cs | 14 + .../SyncListGenericInheritance.cs.meta | 11 + ...stGenericInheritanceWithMultipleGeneric.cs | 17 + ...ericInheritanceWithMultipleGeneric.cs.meta | 11 + .../SyncListInheritance.cs | 15 + .../SyncListInheritance.cs.meta | 11 + .../SyncListMissingParamlessCtor.cs | 14 + .../SyncListMissingParamlessCtor.cs.meta | 11 + ...MissingParamlessCtorManuallyInitialized.cs | 14 + ...ngParamlessCtorManuallyInitialized.cs.meta | 11 + .../SyncListNestedInAbstractClass.cs | 20 + .../SyncListNestedInAbstractClass.cs.meta | 11 + .../SyncListNestedInStruct.cs | 15 + .../SyncListNestedInStruct.cs.meta | 11 + .../SyncListNestedStruct.cs | 17 + .../SyncListNestedStruct.cs.meta | 11 + .../SyncListStruct.cs | 17 + .../SyncListStruct.cs.meta | 11 + .../SyncListErrorForGenericStruct.cs | 17 + .../SyncListErrorForInterface.cs | 14 + .../SyncListGenericStructWithCustomMethods.cs | 27 + .../SyncListInterfaceWithCustomMethods.cs | 31 + ...yncListNestedInAbstractClassWithInvalid.cs | 20 + .../SyncListNestedInStructWithInvalid.cs | 18 + .../Editor/Weaver/WeaverSyncObjectsTests.cs | 21 + .../Weaver/WeaverSyncObjectsTests.cs.meta | 3 + .../WeaverSyncObjectsTests_IsSuccess.meta | 3 + .../SyncObjectsExactlyMax.cs | 80 + .../SyncObjectsExactlyMax.cs.meta | 3 + .../RecommendsReadonly.cs | 10 + .../RecommendsReadonly.cs.meta | 3 + .../SyncObjectsMoreThanMax.cs | 81 + .../SyncObjectsMoreThanMax.cs.meta | 3 + .../Weaver/WeaverSyncSetTests_IsSuccess.meta | 8 + .../WeaverSyncSetTests_IsSuccess/SyncSet.cs | 9 + .../SyncSet.cs.meta | 11 + .../SyncSetByteValid.cs | 11 + .../SyncSetByteValid.cs.meta | 11 + .../SyncSetGenericAbstractInheritance.cs | 13 + .../SyncSetGenericAbstractInheritance.cs.meta | 11 + .../SyncSetGenericInheritance.cs | 13 + .../SyncSetGenericInheritance.cs.meta | 11 + .../SyncSetInheritance.cs | 13 + .../SyncSetInheritance.cs.meta | 11 + .../SyncSetStruct.cs | 17 + .../SyncSetStruct.cs.meta | 11 + .../Weaver/WeaverSyncVarAttributeHookTests.cs | 40 + .../WeaverSyncVarAttributeHookTests.cs.meta | 11 + ...erSyncVarAttributeHookTests_IsSuccess.meta | 3 + .../FindsHookWithGameObjects.cs | 16 + .../FindsHookWithGameObjects.cs.meta | 11 + .../FindsHookWithNetworkIdentity.cs | 16 + .../FindsHookWithNetworkIdentity.cs.meta | 11 + .../FindsHookWithOtherOverloadsInOrder.cs | 21 + ...FindsHookWithOtherOverloadsInOrder.cs.meta | 11 + ...ndsHookWithOtherOverloadsInReverseOrder.cs | 21 + ...okWithOtherOverloadsInReverseOrder.cs.meta | 11 + .../FindsPrivateHook.cs | 15 + .../FindsPrivateHook.cs.meta | 11 + .../FindsPublicHook.cs | 15 + .../FindsPublicHook.cs.meta | 11 + .../FindsStaticHook.cs | 15 + .../FindsStaticHook.cs.meta | 11 + .../ErrorForWrongTypeNewParameter.cs | 15 + .../ErrorForWrongTypeOldParameter.cs | 15 + .../ErrorWhenNoHookFound.cs | 10 + ...rorWhenNoHookWithCorrectParametersFound.cs | 20 + .../Weaver/WeaverSyncVarAttributeTests.cs | 71 + .../WeaverSyncVarAttributeTests.cs.meta | 11 + ...WeaverSyncVarAttributeTests_IsSuccess.meta | 3 + .../SyncVarsDerivedNetworkBehaviour.cs | 14 + .../SyncVarsDerivedNetworkBehaviour.cs.meta | 11 + .../SyncVarsExactlyMax.cs | 74 + .../SyncVarsExactlyMax.cs.meta | 3 + .../SyncVarsValid.cs | 86 + .../SyncVarsValid.cs.meta | 11 + .../SyncVarsCantBeArray.cs | 10 + .../SyncVarsGenericParam.cs | 15 + .../SyncVarsInterface.cs | 14 + .../SyncVarsMoreThanMax.cs | 74 + .../SyncVarsStatic.cs | 10 + .../SyncVarsSyncList.cs | 25 + .../SyncVarsUnityComponent.cs | 11 + .../Editor/Weaver/WeaverTargetRpcTests.cs | 35 + .../Weaver/WeaverTargetRpcTests.cs.meta | 11 + .../WeaverTargetRpcTests_IsSuccess.meta | 3 + .../OverrideVirtualTargetRpc.cs | 23 + .../OverrideVirtualTargetRpc.cs.meta | 11 + ...ParametersWhileSkipingNetworkConnection.cs | 10 + ...etersWhileSkipingNetworkConnection.cs.meta | 11 + .../TargetRpcCanSkipNetworkConnection.cs | 10 + .../TargetRpcCanSkipNetworkConnection.cs.meta | 11 + .../TargetRpcValid.cs | 10 + .../TargetRpcValid.cs.meta | 11 + .../VirtualTargetRpc.cs | 14 + .../VirtualTargetRpc.cs.meta | 11 + .../AbstractTargetRpc.cs | 11 + ...NetworkConnectionIsNotTheFirstParameter.cs | 10 + .../ErrorWhenTargetRpcIsStatic.cs | 10 + .../OverrideAbstractTargetRpc.cs | 20 + .../Mirror/Tests/Editor/Weaver/WeaverTests.cs | 77 + .../Tests/Editor/Weaver/WeaverTests.cs.meta | 11 + .../Weaver/WeaverTestsBuildFromTestName.cs | 40 + .../WeaverTestsBuildFromTestName.cs.meta | 3 + Assets/Mirror/Tests/Runtime.meta | 8 + ...lientSceneTests_DestroyAllClientObjects.cs | 174 + ...SceneTests_DestroyAllClientObjects.cs.meta | 11 + .../Runtime/ClientSceneTests_LocalPlayer.cs | 68 + .../ClientSceneTests_LocalPlayer.cs.meta | 11 + .../ClientSceneTests_LocalPlayer_AsHost.cs | 48 + ...lientSceneTests_LocalPlayer_AsHost.cs.meta | 3 + ...ClientSceneTests_Runtime_RegisterPrefab.cs | 72 + ...tSceneTests_Runtime_RegisterPrefab.cs.meta | 11 + .../Tests/Runtime/Mirror.Tests.Runtime.asmdef | 22 + .../Runtime/Mirror.Tests.Runtime.asmdef.meta | 7 + .../Tests/Runtime/NetworkIdentityTests.cs | 79 + .../Runtime/NetworkIdentityTests.cs.meta | 11 + .../Tests/Runtime/NetworkManagerTests.cs | 45 + .../Tests/Runtime/NetworkManagerTests.cs.meta | 11 + .../Tests/Runtime/NetworkServerRuntimeTest.cs | 112 + .../Runtime/NetworkServerRuntimeTest.cs.meta | 11 + Assets/Mirror/Tests/Runtime/Scenes.meta | 8 + .../SceneObjectSpawningTestsScene.unity | 381 ++ .../SceneObjectSpawningTestsScene.unity.meta | 7 + .../Runtime/Scenes/TestNetworkManager.prefab | 114 + .../Scenes/TestNetworkManager.prefab.meta | 7 + .../Tests/Runtime/Scenes/TestPlayer.prefab | 48 + .../Runtime/Scenes/TestPlayer.prefab.meta | 7 + 560 files changed, 32521 insertions(+) create mode 100644 Assets/Mirror/Tests.meta create mode 100644 Assets/Mirror/Tests/Common.meta create mode 100644 Assets/Mirror/Tests/Common/Castle.Core.dll create mode 100644 Assets/Mirror/Tests/Common/Castle.Core.dll.meta create mode 100644 Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs create mode 100644 Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs.meta create mode 100644 Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs create mode 100644 Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs.meta create mode 100644 Assets/Mirror/Tests/Common/FakeNetworkConnection.cs create mode 100644 Assets/Mirror/Tests/Common/FakeNetworkConnection.cs.meta create mode 100644 Assets/Mirror/Tests/Common/MemoryTransport.cs create mode 100644 Assets/Mirror/Tests/Common/MemoryTransport.cs.meta create mode 100644 Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef create mode 100644 Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef.meta create mode 100644 Assets/Mirror/Tests/Common/MirrorEditModeTest.cs create mode 100644 Assets/Mirror/Tests/Common/MirrorEditModeTest.cs.meta create mode 100644 Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs create mode 100644 Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs.meta create mode 100644 Assets/Mirror/Tests/Common/MirrorTest.cs create mode 100644 Assets/Mirror/Tests/Common/MirrorTest.cs.meta create mode 100644 Assets/Mirror/Tests/Common/NSubstitute.dll create mode 100644 Assets/Mirror/Tests/Common/NSubstitute.dll.meta create mode 100644 Assets/Mirror/Tests/Common/System.Threading.Tasks.Extensions.dll create mode 100644 Assets/Mirror/Tests/Common/System.Threading.Tasks.Extensions.dll.meta create mode 100644 Assets/Mirror/Tests/Editor.meta create mode 100644 Assets/Mirror/Tests/Editor/Batching.meta create mode 100644 Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientRpcTest.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientRpcTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs create mode 100644 Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/CommandOverrideTest.cs create mode 100644 Assets/Mirror/Tests/Editor/CommandOverrideTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/CommandTest.cs create mode 100644 Assets/Mirror/Tests/Editor/CommandTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/CompressionTests.cs create mode 100644 Assets/Mirror/Tests/Editor/CompressionTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/CustomRWTest.cs create mode 100644 Assets/Mirror/Tests/Editor/CustomRWTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs create mode 100644 Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs create mode 100644 Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ExtensionsTest.cs create mode 100644 Assets/Mirror/Tests/Editor/ExtensionsTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs create mode 100644 Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Generated.meta create mode 100644 Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs create mode 100644 Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs create mode 100644 Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Grid2DTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Grid2DTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs create mode 100644 Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/LocalConnectionTest.cs create mode 100644 Assets/Mirror/Tests/Editor/LocalConnectionTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs create mode 100644 Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/MessagePackingTest.cs create mode 100644 Assets/Mirror/Tests/Editor/MessagePackingTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs create mode 100644 Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef create mode 100644 Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef.meta create mode 100644 Assets/Mirror/Tests/Editor/MultiplexTest.cs create mode 100644 Assets/Mirror/Tests/Editor/MultiplexTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkClientTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkClientTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkLoopTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkLoopTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkManagerTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkManagerTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkMessageTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkMessageTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkReaderTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkReaderTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkServerTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkServerTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/NetworkWriterTest.cs create mode 100644 Assets/Mirror/Tests/Editor/NetworkWriterTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/OverloadMethodTest.cs create mode 100644 Assets/Mirror/Tests/Editor/OverloadMethodTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/PoolTests.cs create mode 100644 Assets/Mirror/Tests/Editor/PoolTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/RemoteTestBase.cs create mode 100644 Assets/Mirror/Tests/Editor/RemoteTestBase.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs create mode 100644 Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs create mode 100644 Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs create mode 100644 Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/StructMessagesTests.cs create mode 100644 Assets/Mirror/Tests/Editor/StructMessagesTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncListClassTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncListClassTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncListStructTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncListStructTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncListTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncListTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncSetTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncSetTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/SyncVarTests.cs create mode 100644 Assets/Mirror/Tests/Editor/SyncVarTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs create mode 100644 Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/TargetRpcTest.cs create mode 100644 Assets/Mirror/Tests/Editor/TargetRpcTest.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs.meta create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab.meta create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab.meta create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab create mode 100644 Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab.meta create mode 100644 Assets/Mirror/Tests/Editor/UtilsTests.cs create mode 100644 Assets/Mirror/Tests/Editor/UtilsTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef create mode 100644 Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/README.md create mode 100644 Assets/Mirror/Tests/Editor/Weaver/README.md.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/AbstractClientRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/ClientRpcCantBeStatic.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/OverrideAbstractClientRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnAbstractMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnOverrideMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnVirutalMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourClient.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourServer.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourClient.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourServer.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassClient.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassServer.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnAbstractMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnOverrideMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnVirutalMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassClient.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassServer.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/AbstractCommand.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/CommandCantBeStatic.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForNetworkConnectionThatIsNotSenderConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForOptionalNetworkConnectionThatIsNotSenderConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/OverrideAbstractCommand.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForClassWithNoValidConstructor.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArraySegmentType.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArrayType.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidListType.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForMultidimensionalArray.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingAbstractClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingInterface.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingMonoBehaviour.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingObject.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingScriptableObject.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingTypeInheritedFromMonoBehaviour.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingUnityAsset.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesWarningWhenRegisteringExistingExtensionMethod.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberGeneric.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberInterface.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourClientRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourCommand.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncList.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncVar.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourTargetRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcCoroutine.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcGenericParam.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamAbstract.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamComponent.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOptional.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOut.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamRef.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcVoidReturn.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdCoroutine.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdDuplicateName.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdGenericParam.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamAbstract.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamComponent.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamNetworkConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOptional.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOut.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamRef.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdVoidReturn.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcCoroutine.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcGenericParam.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamAbstract.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamComponent.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOptional.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOut.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamRef.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcVoidReturn.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructItem.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructKey.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForGenericStruct.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForInterface.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListGenericStructWithCustomMethods.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListInterfaceWithCustomMethods.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInAbstractClassWithInvalid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInStructWithInvalid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeNewParameter.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeOldParameter.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookFound.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookWithCorrectParametersFound.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsCantBeArray.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsGenericParam.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsInterface.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsMoreThanMax.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsStatic.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsSyncList.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsUnityComponent.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/AbstractTargetRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenNetworkConnectionIsNotTheFirstParameter.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenTargetRpcIsStatic.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/OverrideAbstractTargetRpc.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs.meta create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs create mode 100644 Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime.meta create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs create mode 100644 Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef create mode 100644 Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef.meta create mode 100644 Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs create mode 100644 Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs create mode 100644 Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs create mode 100644 Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs.meta create mode 100644 Assets/Mirror/Tests/Runtime/Scenes.meta create mode 100644 Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity create mode 100644 Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity.meta create mode 100644 Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab create mode 100644 Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab.meta create mode 100644 Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab create mode 100644 Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab.meta diff --git a/Assets/Mirror/Tests.meta b/Assets/Mirror/Tests.meta new file mode 100644 index 000000000..a519cf7ae --- /dev/null +++ b/Assets/Mirror/Tests.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 4de157ac7e1594c758ce6dc401674f5c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common.meta b/Assets/Mirror/Tests/Common.meta new file mode 100644 index 000000000..cb52e9878 --- /dev/null +++ b/Assets/Mirror/Tests/Common.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: dd4f310377c5a4ad39bd31546c95c2a2 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/Castle.Core.dll b/Assets/Mirror/Tests/Common/Castle.Core.dll new file mode 100644 index 0000000000000000000000000000000000000000..dbc4b29deda4bd4392eba36352344c7ddde2db77 GIT binary patch literal 442368 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�X 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
  • +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 literal 0 HcmV?d00001 diff --git a/Assets/Mirror/Tests/Common/Castle.Core.dll.meta b/Assets/Mirror/Tests/Common/Castle.Core.dll.meta new file mode 100644 index 000000000..5189874d6 --- /dev/null +++ b/Assets/Mirror/Tests/Common/Castle.Core.dll.meta @@ -0,0 +1,112 @@ +fileFormatVersion: 2 +guid: 739a02265856e4e4b8a3ae587a908479 +PluginImporter: + externalObjects: {} + serializedVersion: 2 + iconMap: {} + executionOrder: {} + defineConstraints: [] + isPreloaded: 0 + isOverridable: 0 + isExplicitlyReferenced: 0 + validateReferences: 1 + platformData: + - first: + '': Any + second: + enabled: 0 + settings: + Exclude Android: 1 + Exclude Editor: 0 + Exclude Linux: 1 + Exclude Linux64: 1 + Exclude LinuxUniversal: 1 + Exclude OSXUniversal: 1 + Exclude WebGL: 1 + Exclude Win: 1 + Exclude Win64: 1 + Exclude iOS: 1 + - first: + Android: Android + second: + enabled: 0 + settings: + CPU: ARMv7 + - first: + Any: + second: + enabled: 0 + settings: {} + - first: + Editor: Editor + second: + enabled: 1 + settings: + CPU: AnyCPU + DefaultValueInitialized: true + OS: AnyOS + - first: + Facebook: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Facebook: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Linux + second: + enabled: 0 + settings: + CPU: x86 + - first: + Standalone: Linux64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: LinuxUniversal + second: + enabled: 0 + settings: + CPU: None + - first: + Standalone: OSXUniversal + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Standalone: Win64 + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + Windows Store Apps: WindowsStoreApps + second: + enabled: 0 + settings: + CPU: AnyCPU + - first: + iPhone: iOS + second: + enabled: 0 + settings: + AddToEmbeddedBinaries: false + CompileFlags: + FrameworkDependencies: + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs b/Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs new file mode 100644 index 000000000..2d4a1634e --- /dev/null +++ b/Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs @@ -0,0 +1,62 @@ +using System; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; + +namespace Mirror.Tests +{ + + /// + /// Used by both runtime and edit time tests + /// + [TestFixture] + public abstract class ClientSceneTestsBase : MirrorEditModeTest + { + // use guid to find asset so that the path does not matter + protected const string ValidPrefabAssetGuid = "33169286da0313d45ab5bfccc6cf3775"; + protected const string PrefabWithChildrenAssetGuid = "a78e009e3f2dee44e8859516974ede43"; + protected const string InvalidPrefabAssetGuid = "78f0a3f755d35324e959f3ecdd993fb0"; + // random guid, not used anywhere + protected const string AnotherGuidString = "5794128cdfda04542985151f82990d05"; + + protected GameObject validPrefab; + protected NetworkIdentity validPrefabNetworkIdentity; + protected GameObject prefabWithChildren; + protected GameObject invalidPrefab; + protected Guid validPrefabGuid; + protected Guid anotherGuid; + + static GameObject LoadPrefab(string guid) + { + return AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(guid)); + } + + [OneTimeSetUp] + public void OneTimeSetUp() + { + validPrefab = LoadPrefab(ValidPrefabAssetGuid); + validPrefabNetworkIdentity = validPrefab.GetComponent(); + prefabWithChildren = LoadPrefab(PrefabWithChildrenAssetGuid); + invalidPrefab = LoadPrefab(InvalidPrefabAssetGuid); + validPrefabGuid = new Guid(ValidPrefabAssetGuid); + anotherGuid = new Guid(AnotherGuidString); + } + + [TearDown] + public override void TearDown() + { + // reset asset id in case they are changed by tests + validPrefabNetworkIdentity.assetId = validPrefabGuid; + + base.TearDown(); + } + + [OneTimeTearDown] + public void OneTimeTearDown() + { + validPrefab = null; + prefabWithChildren = null; + invalidPrefab = null; + } + } +} diff --git a/Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs.meta b/Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs.meta new file mode 100644 index 000000000..9be790649 --- /dev/null +++ b/Assets/Mirror/Tests/Common/ClientSceneTestsBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bf741fb5970e5e048aea5ff3e396d24c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs b/Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs new file mode 100644 index 000000000..cd7131049 --- /dev/null +++ b/Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs @@ -0,0 +1,212 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + /// + /// Used by both runtime and edit time tests + /// + [TestFixture] + public abstract class ClientSceneTests_RegisterPrefabBase : ClientSceneTestsBase + { + [Test] + [TestCase(RegisterPrefabOverload.Prefab, false)] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId, true)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate, false)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId, true)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate, false)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId, true)] + public void CheckOverloadWithAssetId(RegisterPrefabOverload overload, bool expected) + { + // test to make sure OverloadWithAssetId correctly works with flags + Assert.That(OverloadWithAssetId(overload), Is.EqualTo(expected)); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab, false)] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId, false)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate, true)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId, true)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate, true)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId, true)] + public void CheckOverloadWithHandler(RegisterPrefabOverload overload, bool expected) + { + // test to make sure OverloadWithHandler correctly works with flags + Assert.That(OverloadWithHandler(overload), Is.EqualTo(expected)); + } + + /// + /// Allows TestCases to call different overloads for RegisterPrefab. + /// Without this we would need duplicate tests for each overload + /// + [Flags] + public enum RegisterPrefabOverload + { + Prefab = 1, + Prefab_NewAssetId = 2, + Prefab_SpawnDelegate = 4, + Prefab_SpawnDelegate_NewAssetId = 8, + Prefab_SpawnHandlerDelegate = 16, + Prefab_SpawnHandlerDelegate_NewAssetId = 32, + + WithAssetId = Prefab_NewAssetId | Prefab_SpawnDelegate_NewAssetId | Prefab_SpawnHandlerDelegate_NewAssetId, + WithHandler = Prefab_SpawnDelegate | Prefab_SpawnDelegate_NewAssetId | Prefab_SpawnHandlerDelegate | Prefab_SpawnHandlerDelegate_NewAssetId + } + + protected static bool OverloadWithAssetId(RegisterPrefabOverload overload) + { + return (overload & RegisterPrefabOverload.WithAssetId) != 0; + } + + protected static bool OverloadWithHandler(RegisterPrefabOverload overload) + { + return (overload & RegisterPrefabOverload.WithHandler) != 0; + } + + protected void CallRegisterPrefab(GameObject prefab, RegisterPrefabOverload overload) + { + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + SpawnHandlerDelegate spawnHandlerDelegate = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + switch (overload) + { + case RegisterPrefabOverload.Prefab: + NetworkClient.RegisterPrefab(prefab); + break; + case RegisterPrefabOverload.Prefab_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid); + break; + case RegisterPrefabOverload.Prefab_SpawnDelegate: + NetworkClient.RegisterPrefab(prefab, spawnHandler, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid, spawnHandler, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate: + NetworkClient.RegisterPrefab(prefab, spawnHandlerDelegate, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid, spawnHandlerDelegate, unspawnHandler); + break; + + default: + Debug.LogError("Overload not found"); + break; + } + } + + protected void CallRegisterPrefab(GameObject prefab, RegisterPrefabOverload overload, Guid guid) + { + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + SpawnHandlerDelegate spawnHandlerDelegate = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + switch (overload) + { + case RegisterPrefabOverload.Prefab_NewAssetId: + NetworkClient.RegisterPrefab(prefab, guid); + break; + case RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, guid, spawnHandler, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, guid, spawnHandlerDelegate, unspawnHandler); + break; + + case RegisterPrefabOverload.Prefab: + case RegisterPrefabOverload.Prefab_SpawnDelegate: + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate: + Debug.LogError("Overload did not have guid parameter"); + break; + default: + Debug.LogError("Overload not found"); + break; + } + } + + protected void CallRegisterPrefab(GameObject prefab, RegisterPrefabOverload overload, SpawnDelegate spawnHandler) + { + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + switch (overload) + { + case RegisterPrefabOverload.Prefab_SpawnDelegate: + NetworkClient.RegisterPrefab(prefab, spawnHandler, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid, spawnHandler, unspawnHandler); + break; + + case RegisterPrefabOverload.Prefab: + case RegisterPrefabOverload.Prefab_NewAssetId: + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate: + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId: + Debug.LogError("Overload did not have SpawnDelegate parameter"); + break; + default: + Debug.LogError("Overload not found"); + break; + } + } + + protected void CallRegisterPrefab(GameObject prefab, RegisterPrefabOverload overload, SpawnHandlerDelegate spawnHandlerDelegate) + { + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + switch (overload) + { + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate: + NetworkClient.RegisterPrefab(prefab, spawnHandlerDelegate, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid, spawnHandlerDelegate, unspawnHandler); + break; + + case RegisterPrefabOverload.Prefab: + case RegisterPrefabOverload.Prefab_NewAssetId: + case RegisterPrefabOverload.Prefab_SpawnDelegate: + case RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId: + Debug.LogError("Overload did not have SpawnHandlerDelegate parameter"); + break; + default: + Debug.LogError("Overload not found"); + break; + } + } + + protected void CallRegisterPrefab(GameObject prefab, RegisterPrefabOverload overload, UnSpawnDelegate unspawnHandler) + { + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + SpawnHandlerDelegate spawnHandlerDelegate = new SpawnHandlerDelegate(x => null); + + switch (overload) + { + + case RegisterPrefabOverload.Prefab_SpawnDelegate: + NetworkClient.RegisterPrefab(prefab, spawnHandler, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid, spawnHandler, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate: + NetworkClient.RegisterPrefab(prefab, spawnHandlerDelegate, unspawnHandler); + break; + case RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId: + NetworkClient.RegisterPrefab(prefab, anotherGuid, spawnHandlerDelegate, unspawnHandler); + break; + + case RegisterPrefabOverload.Prefab: + case RegisterPrefabOverload.Prefab_NewAssetId: + Debug.LogError("Overload did not have UnSpawnDelegate parameter"); + break; + default: + Debug.LogError("Overload not found"); + break; + } + } + + protected Guid GuidForOverload(RegisterPrefabOverload overload) => OverloadWithAssetId(overload) ? anotherGuid : validPrefabGuid; + } +} diff --git a/Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs.meta b/Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs.meta new file mode 100644 index 000000000..8324dc240 --- /dev/null +++ b/Assets/Mirror/Tests/Common/ClientSceneTests_RegisterPrefabBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8eb53689226946640bc49bb962b13638 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/FakeNetworkConnection.cs b/Assets/Mirror/Tests/Common/FakeNetworkConnection.cs new file mode 100644 index 000000000..58ea0a434 --- /dev/null +++ b/Assets/Mirror/Tests/Common/FakeNetworkConnection.cs @@ -0,0 +1,12 @@ +using System; + +namespace Mirror.Tests +{ + public class FakeNetworkConnection : NetworkConnectionToClient + { + public FakeNetworkConnection() : base(1) {} + public override string address => "Test"; + public override void Disconnect() {} + internal override void Send(ArraySegment segment, int channelId = 0) {} + } +} diff --git a/Assets/Mirror/Tests/Common/FakeNetworkConnection.cs.meta b/Assets/Mirror/Tests/Common/FakeNetworkConnection.cs.meta new file mode 100644 index 000000000..129765a98 --- /dev/null +++ b/Assets/Mirror/Tests/Common/FakeNetworkConnection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 935b1eb49c500674eaaf88982952a69e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/MemoryTransport.cs b/Assets/Mirror/Tests/Common/MemoryTransport.cs new file mode 100644 index 000000000..6f0ace3bc --- /dev/null +++ b/Assets/Mirror/Tests/Common/MemoryTransport.cs @@ -0,0 +1,208 @@ +// memory transport for easier testing +// note: file needs to be outside of Editor folder, otherwise AddComponent +// can't be called with MemoryTransport +using System; +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror.Tests +{ + public class MemoryTransport : Transport + { + public enum EventType { Connected, Data, Disconnected } + public struct Message + { + public int connectionId; + public EventType eventType; + public byte[] data; + public Message(int connectionId, EventType eventType, byte[] data) + { + this.connectionId = connectionId; + this.eventType = eventType; + this.data = data; + } + } + + bool clientConnected; + public Queue clientIncoming = new Queue(); + bool serverActive; + public Queue serverIncoming = new Queue(); + + public override bool Available() => true; + // limit max size to something reasonable so pool doesn't allocate + // int.MaxValue = 2GB each time. + public override int GetMaxPacketSize(int channelId) => ushort.MaxValue; + // 1400 max batch size + // -> need something != GetMaxPacketSize for testing + // -> MTU aka 1400 is used a lot anyway + public override int GetBatchThreshold(int channelId) => 1400; + public override void Shutdown() {} + public override bool ClientConnected() => clientConnected; + public override void ClientConnect(string address) + { + // only if server is running + if (serverActive) + { + // add server connected message with connId=1 because 0 is reserved + serverIncoming.Enqueue(new Message(1, EventType.Connected, null)); + + // add client connected message + clientIncoming.Enqueue(new Message(0, EventType.Connected, null)); + + clientConnected = true; + } + } + public override void ClientSend(ArraySegment segment, int channelId) + { + // only if client connected + if (clientConnected) + { + // a real transport fails for > max sized messages. + // mirror checks it, but let's guarantee that we catch > max + // sized message send attempts just like a real transport would. + // => helps to cover packet size issues i.e. for timestamp + // batching tests + int max = GetMaxPacketSize(channelId); + if (segment.Count > max) + throw new Exception($"MemoryTransport ClientSend of {segment.Count} bytes exceeds max of {max} bytes"); + + // copy segment data because it's only valid until return + byte[] data = new byte[segment.Count]; + Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count); + + // add server data message with connId=1 because 0 is reserved + serverIncoming.Enqueue(new Message(1, EventType.Data, data)); + } + } + public override void ClientDisconnect() + { + // only if client connected + if (clientConnected) + { + // clear all pending messages that we may have received. + // over the wire, we wouldn't receive any more pending messages + // ether after calling disconnect. + clientIncoming.Clear(); + + // add server disconnected message with connId=1 because 0 is reserved + serverIncoming.Enqueue(new Message(1, EventType.Disconnected, null)); + + // add client disconnected message + clientIncoming.Enqueue(new Message(0, EventType.Disconnected, null)); + + // not connected anymore + clientConnected = false; + } + } + // messages should always be processed in early update + public override void ClientEarlyUpdate() + { + // note: process even if not connected because when calling + // Disconnect, we add a Disconnected event which still needs to be + // processed here. + while (clientIncoming.Count > 0) + { + Message message = clientIncoming.Dequeue(); + switch (message.eventType) + { + case EventType.Connected: + Debug.Log("MemoryTransport Client Message: Connected"); + OnClientConnected.Invoke(); + break; + case EventType.Data: + Debug.Log($"MemoryTransport Client Message: Data: {BitConverter.ToString(message.data)}"); + OnClientDataReceived.Invoke(new ArraySegment(message.data), 0); + break; + case EventType.Disconnected: + Debug.Log("MemoryTransport Client Message: Disconnected"); + OnClientDisconnected.Invoke(); + break; + } + } + } + + public override bool ServerActive() => serverActive; + public override Uri ServerUri() => throw new NotImplementedException(); + public override void ServerStart() { serverActive = true; } + public override void ServerSend(int connectionId, ArraySegment segment, int channelId) + { + // only if server is running and client is connected + if (serverActive && clientConnected) + { + // a real transport fails for > max sized messages. + // mirror checks it, but let's guarantee that we catch > max + // sized message send attempts just like a real transport would. + // => helps to cover packet size issues i.e. for timestamp + // batching tests + int max = GetMaxPacketSize(channelId); + if (segment.Count > max) + throw new Exception($"MemoryTransport ServerSend of {segment.Count} bytes exceeds max of {max} bytes"); + + // copy segment data because it's only valid until return + byte[] data = new byte[segment.Count]; + Array.Copy(segment.Array, segment.Offset, data, 0, segment.Count); + + // add client data message + clientIncoming.Enqueue(new Message(0, EventType.Data, data)); + } + } + + public override void ServerDisconnect(int connectionId) + { + // clear all pending messages that we may have received. + // over the wire, we wouldn't receive any more pending messages + // ether after calling disconnect. + serverIncoming.Clear(); + + // add client disconnected message with connectionId + clientIncoming.Enqueue(new Message(connectionId, EventType.Disconnected, null)); + + // add server disconnected message with connectionId + serverIncoming.Enqueue(new Message(connectionId, EventType.Disconnected, null)); + + // not active anymore + serverActive = false; + } + + public override string ServerGetClientAddress(int connectionId) => string.Empty; + public override void ServerStop() + { + // clear all pending messages that we may have received. + // over the wire, we wouldn't receive any more pending messages + // ether after calling stop. + serverIncoming.Clear(); + + // add client disconnected message + clientIncoming.Enqueue(new Message(0, EventType.Disconnected, null)); + + // add server disconnected message with connId=1 because 0 is reserved + serverIncoming.Enqueue(new Message(1, EventType.Disconnected, null)); + + // not active anymore + serverActive = false; + } + // messages should always be processed in early update + public override void ServerEarlyUpdate() + { + while (serverIncoming.Count > 0) + { + Message message = serverIncoming.Dequeue(); + switch (message.eventType) + { + case EventType.Connected: + Debug.Log("MemoryTransport Server Message: Connected"); + OnServerConnected.Invoke(message.connectionId); + break; + case EventType.Data: + Debug.Log($"MemoryTransport Server Message: Data: {BitConverter.ToString(message.data)}"); + OnServerDataReceived.Invoke(message.connectionId, new ArraySegment(message.data), 0); + break; + case EventType.Disconnected: + Debug.Log("MemoryTransport Server Message: Disconnected"); + OnServerDisconnected.Invoke(message.connectionId); + break; + } + } + } + } +} diff --git a/Assets/Mirror/Tests/Common/MemoryTransport.cs.meta b/Assets/Mirror/Tests/Common/MemoryTransport.cs.meta new file mode 100644 index 000000000..d624b49ff --- /dev/null +++ b/Assets/Mirror/Tests/Common/MemoryTransport.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6131cf1e8a1c14ef5b5253f86f3fc9c9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef b/Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef new file mode 100644 index 000000000..ca39e3a96 --- /dev/null +++ b/Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef @@ -0,0 +1,16 @@ +{ + "name": "Mirror.Tests.Common", + "references": [ + "Mirror" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef.meta b/Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef.meta new file mode 100644 index 000000000..e06568a88 --- /dev/null +++ b/Assets/Mirror/Tests/Common/Mirror.Tests.Common.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4e9aca8a359ab48de906aedbfa1ffe21 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/MirrorEditModeTest.cs b/Assets/Mirror/Tests/Common/MirrorEditModeTest.cs new file mode 100644 index 000000000..57db52499 --- /dev/null +++ b/Assets/Mirror/Tests/Common/MirrorEditModeTest.cs @@ -0,0 +1,14 @@ +// base class for networking tests to make things easier. +using NUnit.Framework; + +namespace Mirror.Tests +{ + public abstract class MirrorEditModeTest : MirrorTest + { + [SetUp] + public override void SetUp() => base.SetUp(); + + [TearDown] + public override void TearDown() => base.TearDown(); + } +} diff --git a/Assets/Mirror/Tests/Common/MirrorEditModeTest.cs.meta b/Assets/Mirror/Tests/Common/MirrorEditModeTest.cs.meta new file mode 100644 index 000000000..2f51f7e36 --- /dev/null +++ b/Assets/Mirror/Tests/Common/MirrorEditModeTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a6110480a9c07423290301aedafb2a93 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs b/Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs new file mode 100644 index 000000000..a63dc836a --- /dev/null +++ b/Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs @@ -0,0 +1,27 @@ +// base class for networking tests to make things easier. +using System.Collections; +using UnityEngine.TestTools; + +namespace Mirror.Tests +{ + public abstract class MirrorPlayModeTest : MirrorTest + { + // when overwriting, call it like this: + // yield return base.UnitySetUp(); + [UnitySetUp] + public virtual IEnumerator UnitySetUp() + { + base.SetUp(); + yield return null; + } + + // when overwriting, call it like this: + // yield return base.UnityTearDown(); + [UnityTearDown] + public virtual IEnumerator UnityTearDown() + { + base.TearDown(); + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs.meta b/Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs.meta new file mode 100644 index 000000000..5314431b2 --- /dev/null +++ b/Assets/Mirror/Tests/Common/MirrorPlayModeTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: be3f9e24bcdb748728f846e88eea29f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/MirrorTest.cs b/Assets/Mirror/Tests/Common/MirrorTest.cs new file mode 100644 index 000000000..b8b9ccd25 --- /dev/null +++ b/Assets/Mirror/Tests/Common/MirrorTest.cs @@ -0,0 +1,557 @@ +// base class for networking tests to make things easier. +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + // inherited by MirrorEditModeTest / MirrorPlayModeTest + // to call SetUp/TearDown by [SetUp]/[UnitySetUp] as needed + public abstract class MirrorTest + { + // keep track of networked GameObjects so we don't have to clean them + // up manually each time. + // CreateNetworked() adds to the list automatically. + public List instantiated; + + // we usually need the memory transport + public MemoryTransport transport; + + public virtual void SetUp() + { + instantiated = new List(); + + // need a transport to send & receive + Transport.activeTransport = transport = new GameObject().AddComponent(); + } + + public virtual void TearDown() + { + NetworkClient.Shutdown(); + NetworkServer.Shutdown(); + + // some tests might modify NetworkServer.connections without ever + // starting the server. + // NetworkServer.Shutdown() only clears connections if it was started. + // so let's do it manually for proper test cleanup here. + NetworkServer.connections.Clear(); + + foreach (GameObject go in instantiated) + if (go != null) + GameObject.DestroyImmediate(go); + + GameObject.DestroyImmediate(transport.gameObject); + Transport.activeTransport = null; + } + + // create a tracked GameObject for tests without Networkidentity + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateGameObject(out GameObject go) + { + go = new GameObject(); + // track + instantiated.Add(go); + } + + // create GameObject + MonoBehaviour + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateGameObject(out GameObject go, out T component) + where T : MonoBehaviour + { + CreateGameObject(out go); + component = go.AddComponent(); + } + + // create GameObject + NetworkIdentity + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworked(out GameObject go, out NetworkIdentity identity) + { + go = new GameObject(); + identity = go.AddComponent(); + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(go); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T component) + where T : NetworkBehaviour + { + go = new GameObject(); + identity = go.AddComponent(); + component = go.AddComponent(); + // always set syncinterval = 0 for immediate testing + component.syncInterval = 0; + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(go); + } + + // create GameObject + NetworkIdentity + 2x NetworkBehaviour + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB) + where T : NetworkBehaviour + where U : NetworkBehaviour + { + go = new GameObject(); + identity = go.AddComponent(); + componentA = go.AddComponent(); + componentB = go.AddComponent(); + // always set syncinterval = 0 for immediate testing + componentA.syncInterval = 0; + componentB.syncInterval = 0; + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(go); + } + + // create GameObject + NetworkIdentity + 2x NetworkBehaviour + // add to tracker list if needed (useful for cleanups afterwards) + protected void CreateNetworked(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC) + where T : NetworkBehaviour + where U : NetworkBehaviour + where V : NetworkBehaviour + { + go = new GameObject(); + identity = go.AddComponent(); + componentA = go.AddComponent(); + componentB = go.AddComponent(); + componentC = go.AddComponent(); + // always set syncinterval = 0 for immediate testing + componentA.syncInterval = 0; + componentB.syncInterval = 0; + componentC.syncInterval = 0; + // Awake is only called in play mode. + // call manually for initialization. + identity.Awake(); + // track + instantiated.Add(go); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, NetworkConnection ownerConnection = null) + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworked(out go, out identity); + + // spawn + NetworkServer.Spawn(go, ownerConnection); + ProcessMessages(); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedAndSpawn( + out GameObject serverGO, out NetworkIdentity serverIdentity, + out GameObject clientGO, out NetworkIdentity clientIdentity, + NetworkConnection ownerConnection = null) + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworked(out serverGO, out serverIdentity); + CreateNetworked(out clientGO, out clientIdentity); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverGO, ownerConnection); + ProcessMessages(); + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T component, NetworkConnection ownerConnection = null) + where T : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworked(out go, out identity, out component); + + // spawn + NetworkServer.Spawn(go, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + Debug.Assert(component.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedAndSpawn( + out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponent, + out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponent, + NetworkConnection ownerConnection = null) + where T : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworked(out serverGO, out serverIdentity, out serverComponent); + CreateNetworked(out clientGO, out clientIdentity, out clientComponent); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverGO, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + Debug.Assert(serverComponent.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, NetworkConnection ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworked(out go, out identity, out componentA, out componentB); + + // spawn + NetworkServer.Spawn(go, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(componentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(componentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedAndSpawn( + out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, + out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, + NetworkConnection ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworked(out serverGO, out serverIdentity, out serverComponentA, out serverComponentB); + CreateNetworked(out clientGO, out clientIdentity, out clientComponentA, out clientComponentB); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverGO, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(serverComponentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(serverComponentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + protected void CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out T componentA, out U componentB, out V componentC, NetworkConnection ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + where V : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + CreateNetworked(out go, out identity, out componentA, out componentB, out componentC); + + // spawn + NetworkServer.Spawn(go, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(componentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(componentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(componentC.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN + // => ownerConnection can be NetworkServer.localConnection if needed. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedAndSpawn( + out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponentA, out U serverComponentB, out V serverComponentC, + out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponentA, out U clientComponentB, out V clientComponentC, + NetworkConnection ownerConnection = null) + where T : NetworkBehaviour + where U : NetworkBehaviour + where V : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworked(out serverGO, out serverIdentity, out serverComponentA, out serverComponentB, out serverComponentC); + CreateNetworked(out clientGO, out clientIdentity, out clientComponentA, out clientComponentB, out clientComponentC); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // spawn + NetworkServer.Spawn(serverGO, ownerConnection); + ProcessMessages(); + + // double check that we have authority if we passed an owner connection + if (ownerConnection != null) + { + Debug.Assert(serverComponentA.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(serverComponentB.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + Debug.Assert(serverComponentC.hasAuthority == true, $"Behaviour Had Wrong Authority when spawned, This means that the test is broken and will give the wrong results"); + } + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. + // often times, we really need a player object for the client to receive + // certain messages. + protected void CreateNetworkedAndSpawnPlayer(out GameObject go, out NetworkIdentity identity, NetworkConnection ownerConnection) + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create a networked object + CreateNetworked(out go, out identity); + + // add as player & process spawn message on client. + NetworkServer.AddPlayerForConnection(ownerConnection, go); + ProcessMessages(); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. + // often times, we really need a player object for the client to receive + // certain messages. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedAndSpawnPlayer( + out GameObject serverGO, out NetworkIdentity serverIdentity, + out GameObject clientGO, out NetworkIdentity clientIdentity, + NetworkConnection ownerConnection) + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworked(out serverGO, out serverIdentity); + CreateNetworked(out clientGO, out clientIdentity); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // add as player & process spawn message on client. + NetworkServer.AddPlayerForConnection(ownerConnection, serverGO); + ProcessMessages(); + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. + // often times, we really need a player object for the client to receive + // certain messages. + protected void CreateNetworkedAndSpawnPlayer(out GameObject go, out NetworkIdentity identity, out T component, NetworkConnection ownerConnection) + where T : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create a networked object + CreateNetworked(out go, out identity, out component); + + // add as player & process spawn message on client. + NetworkServer.AddPlayerForConnection(ownerConnection, go); + ProcessMessages(); + } + + // create GameObject + NetworkIdentity + NetworkBehaviour & SPAWN PLAYER. + // often times, we really need a player object for the client to receive + // certain messages. + // => returns objects from client and from server. + // will be same in host mode. + protected void CreateNetworkedAndSpawnPlayer( + out GameObject serverGO, out NetworkIdentity serverIdentity, out T serverComponent, + out GameObject clientGO, out NetworkIdentity clientIdentity, out T clientComponent, + NetworkConnection ownerConnection) + where T : NetworkBehaviour + { + // server & client need to be active before spawning + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // create one on server, one on client + // (spawning has to find it on client, it doesn't create it) + CreateNetworked(out serverGO, out serverIdentity, out serverComponent); + CreateNetworked(out clientGO, out clientIdentity, out clientComponent); + + // give both a scene id and register it on client for spawnables + clientIdentity.sceneId = serverIdentity.sceneId = (ulong)serverGO.GetHashCode(); + NetworkClient.spawnableObjects[clientIdentity.sceneId] = clientIdentity; + + // add as player & process spawn message on client. + NetworkServer.AddPlayerForConnection(ownerConnection, serverGO); + ProcessMessages(); + + // make sure the client really spawned it. + Assert.That(NetworkClient.spawned.ContainsKey(serverIdentity.netId)); + } + + // fully connect client to local server + // gives out the server's connection to client for convenience if needed + protected void ConnectClientBlocking(out NetworkConnectionToClient connectionToClient) + { + NetworkClient.Connect("127.0.0.1"); + UpdateTransport(); + + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + connectionToClient = NetworkServer.connections.Values.First(); + } + + // fully connect client to local server & authenticate + protected void ConnectClientBlockingAuthenticated(out NetworkConnectionToClient connectionToClient) + { + ConnectClientBlocking(out connectionToClient); + + // authenticate server & client connections + connectionToClient.isAuthenticated = true; + NetworkClient.connection.isAuthenticated = true; + } + + // fully connect client to local server & authenticate & set read + protected void ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient) + { + ConnectClientBlocking(out connectionToClient); + + // authenticate server & client connections + connectionToClient.isAuthenticated = true; + NetworkClient.connection.isAuthenticated = true; + + // set ready + NetworkClient.Ready(); + ProcessMessages(); + Assert.That(connectionToClient.isReady, Is.True); + } + + // fully connect HOST client to local server + // sets NetworkServer.localConnection / NetworkClient.connection. + protected void ConnectHostClientBlocking() + { + NetworkClient.ConnectHost(); + NetworkClient.ConnectLocalServer(); + UpdateTransport(); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + } + + // fully connect client to local server & authenticate & set read + protected void ConnectHostClientBlockingAuthenticatedAndReady() + { + ConnectHostClientBlocking(); + + // authenticate server & client connections + NetworkServer.localConnection.isAuthenticated = true; + NetworkClient.connection.isAuthenticated = true; + + // set ready + NetworkClient.Ready(); + ProcessMessages(); + Assert.That(NetworkServer.localConnection.isReady, Is.True); + } + + protected void UpdateTransport() + { + transport.ClientEarlyUpdate(); + transport.ServerEarlyUpdate(); + } + + protected void ProcessMessages() + { + // server & client need to be active + Debug.Assert(NetworkClient.active, "NetworkClient needs to be active before spawning."); + Debug.Assert(NetworkServer.active, "NetworkServer needs to be active before spawning."); + + // update server & client so batched messages are flushed + NetworkClient.NetworkLateUpdate(); + NetworkServer.NetworkLateUpdate(); + + // update transport so sent messages are received + UpdateTransport(); + } + + // helper function to create local connection pair + protected void CreateLocalConnectionPair(out LocalConnectionToClient connectionToClient, out LocalConnectionToServer connectionToServer) + { + connectionToClient = new LocalConnectionToClient(); + connectionToServer = new LocalConnectionToServer(); + connectionToClient.connectionToServer = connectionToServer; + connectionToServer.connectionToClient = connectionToClient; + } + } +} diff --git a/Assets/Mirror/Tests/Common/MirrorTest.cs.meta b/Assets/Mirror/Tests/Common/MirrorTest.cs.meta new file mode 100644 index 000000000..d7b13dbe2 --- /dev/null +++ b/Assets/Mirror/Tests/Common/MirrorTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 67c5177a4b35749b8b9c4ca7107d8c25 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Common/NSubstitute.dll b/Assets/Mirror/Tests/Common/NSubstitute.dll new file mode 100644 index 0000000000000000000000000000000000000000..010169440b91a22a446ebfadac1fdcca5b385e96 GIT binary patch literal 148480 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 NHqOW%ckz~`;b$& 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 uFih!a$gz7Dg1^`###~GatUT(message)); + } + + [Test] + public void MakeNextBatch_OnlyAcceptsFreshWriter() + { + batcher.AddMessage(new ArraySegment(new byte[]{0x01})); + + writer.WriteByte(0); + Assert.Throws(() => { + batcher.MakeNextBatch(writer, TimeStamp); + }); + } + + [Test] + public void MakeNextBatch_NoMessage() + { + // make batch with no message + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(false)); + } + + [Test] + public void MakeNextBatch_OneMessage() + { + // add message + byte[] message = {0x01, 0x02}; + batcher.AddMessage(new ArraySegment(message)); + + // make batch + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, message))); + } + + [Test] + public void MakeNextBatch_MultipleMessages_AlmostFullBatch() + { + batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02})); + batcher.AddMessage(new ArraySegment(new byte[]{0x03})); + + // make batch + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02, 0x03}))); + + // there should be no more batches to make + Assert.That(batcher.MakeNextBatch(writer, TimeStamp), Is.False); + } + + [Test] + public void MakeNextBatch_MultipleMessages_ExactlyFullBatch() + { + batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02})); + batcher.AddMessage(new ArraySegment(new byte[]{0x03, 0x04})); + + // make batch + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02, 0x03, 0x04}))); + + // there should be no more batches to make + Assert.That(batcher.MakeNextBatch(writer, TimeStamp), Is.False); + } + + [Test] + public void MakeNextBatch_MultipleMessages_MoreThanOneBatch() + { + batcher.AddMessage(new ArraySegment(new byte[]{0x01, 0x02})); + batcher.AddMessage(new ArraySegment(new byte[]{0x03, 0x04})); + batcher.AddMessage(new ArraySegment(new byte[]{0x05})); + + // first batch + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02, 0x03, 0x04}))); + + // reset writer + writer.Position = 0; + + // second batch + result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x05}))); + } + + [Test] + public void MakeNextBatch_MultipleMessages_Small_Giant_Small() + { + // small, too big to include in batch, small + batcher.AddMessage(new ArraySegment(new byte[]{0x01})); + batcher.AddMessage(new ArraySegment(new byte[]{0x02, 0x03, 0x04, 0x05})); + batcher.AddMessage(new ArraySegment(new byte[]{0x06, 0x07})); + + // first batch + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01}))); + + // reset writer + writer.Position = 0; + + // second batch + result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x02, 0x03, 0x04, 0x05}))); + + // reset writer + writer.Position = 0; + + // third batch + result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + + // check result: <> + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x06, 0x07}))); + } + + // messages > threshold should simply be single batches. + // those need to be supported too, for example: + // kcp prefers MTU sized batches + // but we still allow up to 144 KB max message size + [Test] + public void MakeNextBatch_LargerThanThreshold() + { + // make a larger than threshold message + byte[] large = new byte[Threshold + 1]; + for (int i = 0; i < Threshold + 1; ++i) + large[i] = (byte)i; + batcher.AddMessage(new ArraySegment(large)); + + // result should be only the large message + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, large))); + } + + // messages > threshold should simply be single batches. + // those need to be supported too, for example: + // kcp prefers MTU sized batches + // but we still allow up to 144 KB max message size + [Test] + public void MakeNextBatch_LargerThanThreshold_BetweenSmallerMessages() + { + // make a larger than threshold message + byte[] large = new byte[Threshold + 1]; + for (int i = 0; i < Threshold + 1; ++i) + large[i] = (byte)i; + + // add two small, one large, two small messages. + // to make sure everything around it is still batched, + // and the large one is a separate batch. + batcher.AddMessage(new ArraySegment(new byte[]{0x01})); + batcher.AddMessage(new ArraySegment(new byte[]{0x02})); + batcher.AddMessage(new ArraySegment(large)); + batcher.AddMessage(new ArraySegment(new byte[]{0x03})); + batcher.AddMessage(new ArraySegment(new byte[]{0x04})); + + // first batch should be the two small messages + bool result = batcher.MakeNextBatch(writer, TimeStamp); + Assert.That(result, Is.EqualTo(true)); + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02}))); + + // reset writer + writer.Position = 0; + + // second batch should be only the large message + result = batcher.MakeNextBatch(writer, TimeStamp + 1); + Assert.That(result, Is.EqualTo(true)); + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp + 1, large))); + + // reset writer + writer.Position = 0; + + // third batch be the two small messages + result = batcher.MakeNextBatch(writer, TimeStamp + 2); + Assert.That(result, Is.EqualTo(true)); + Assert.That(writer.ToArray().SequenceEqual(ConcatTimestamp(TimeStamp + 2, new byte[]{0x03, 0x04}))); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs.meta b/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs.meta new file mode 100644 index 000000000..9d19de00b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Batching/BatcherTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 787d83b7e2ca4547aca251617d91f7d8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs b/Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs new file mode 100644 index 000000000..e4b7e02ab --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs @@ -0,0 +1,138 @@ +using System; +using NUnit.Framework; + +namespace Mirror.Tests.Batching +{ + public class UnbatcherTests + { + Unbatcher unbatcher; + const double TimeStamp = Math.PI; + + [SetUp] + public void SetUp() + { + unbatcher = new Unbatcher(); + } + + [Test] + public void GetNextMessage_NoBatches() + { + bool result = unbatcher.GetNextMessage(out _, out _); + Assert.That(result, Is.False); + } + + // test for nimoyd bug, where calling getnextmessage after the previous + // call already returned false would cause an InvalidOperationException. + [Test] + public void GetNextMessage_True_False_False_InvalidOperationException() + { + // add batch + byte[] batch = BatcherTests.ConcatTimestamp(TimeStamp, new byte[2]); + unbatcher.AddBatch(new ArraySegment(batch)); + + // get next message, pretend we read the whole thing + bool result = unbatcher.GetNextMessage(out NetworkReader reader, out _); + Assert.That(result, Is.True); + reader.Position = reader.Length; + + // shouldn't get another one + result = unbatcher.GetNextMessage(out reader, out _); + Assert.That(result, Is.False); + + // calling it again was causing "InvalidOperationException: Queue empty" + result = unbatcher.GetNextMessage(out reader, out _); + Assert.That(result, Is.False); + } + + [Test] + public void GetNextMessage_OneBatch() + { + // add one batch + byte[] batch = BatcherTests.ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02}); + unbatcher.AddBatch(new ArraySegment(batch)); + + // get next message, read first byte + bool result = unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimeStamp); + Assert.That(result, Is.True); + Assert.That(reader.ReadByte(), Is.EqualTo(0x01)); + Assert.That(remoteTimeStamp, Is.EqualTo(TimeStamp)); + + // get next message, read last byte + result = unbatcher.GetNextMessage(out reader, out remoteTimeStamp); + Assert.That(result, Is.True); + Assert.That(reader.ReadByte(), Is.EqualTo(0x02)); + Assert.That(remoteTimeStamp, Is.EqualTo(TimeStamp)); + + // there should be no more messages + result = unbatcher.GetNextMessage(out _, out _); + Assert.That(result, Is.False); + } + + [Test] + public void GetNextMessage_MultipleBatches() + { + // add first batch + byte[] firstBatch = BatcherTests.ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02}); + unbatcher.AddBatch(new ArraySegment(firstBatch)); + + // add second batch + byte[] secondBatch = BatcherTests.ConcatTimestamp(TimeStamp + 1, new byte[]{0x03, 0x04}); + unbatcher.AddBatch(new ArraySegment(secondBatch)); + + // get next message, read everything + bool result = unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimeStamp); + Assert.That(result, Is.True); + Assert.That(reader.ReadByte(), Is.EqualTo(0x01)); + Assert.That(reader.ReadByte(), Is.EqualTo(0x02)); + Assert.That(remoteTimeStamp, Is.EqualTo(TimeStamp)); + + // get next message, should point to next batch at Timestamp + 1 + result = unbatcher.GetNextMessage(out reader, out remoteTimeStamp); + Assert.That(result, Is.True); + Assert.That(reader.ReadByte(), Is.EqualTo(0x03)); + Assert.That(reader.ReadByte(), Is.EqualTo(0x04)); + Assert.That(remoteTimeStamp, Is.EqualTo(TimeStamp + 1)); + + // there should be no more messages + result = unbatcher.GetNextMessage(out _, out _); + Assert.That(result, Is.False); + } + + // make sure that retiring a batch, then adding a new batch works. + // previously there was a bug where the batch was retired, + // the reader still pointed to the old batch with pos=len, + // a new batch was added + // GetNextMessage() still returned false because reader still pointed to + // the old batch with pos=len. + [Test] + public void RetireBatchAndTryNewBatch() + { + // add first batch + byte[] firstBatch = BatcherTests.ConcatTimestamp(TimeStamp, new byte[]{0x01, 0x02}); + unbatcher.AddBatch(new ArraySegment(firstBatch)); + + // read everything + bool result = unbatcher.GetNextMessage(out NetworkReader reader, out double remoteTimeStamp); + Assert.That(result, Is.True); + Assert.That(reader.ReadByte(), Is.EqualTo(0x01)); + Assert.That(reader.ReadByte(), Is.EqualTo(0x02)); + Assert.That(remoteTimeStamp, Is.EqualTo(TimeStamp)); + + // try to read again. + // reader will be at limit, which should retire the batch. + result = unbatcher.GetNextMessage(out _, out _); + Assert.That(result, Is.False); + + // add new batch + byte[] secondBatch = BatcherTests.ConcatTimestamp(TimeStamp + 1, new byte[]{0x03, 0x04}); + unbatcher.AddBatch(new ArraySegment(secondBatch)); + + // read everything + result = unbatcher.GetNextMessage(out reader, out remoteTimeStamp); + Assert.That(result, Is.True); + Assert.That(reader.ReadByte(), Is.EqualTo(0x03)); + Assert.That(reader.ReadByte(), Is.EqualTo(0x04)); + Assert.That(remoteTimeStamp, Is.EqualTo(TimeStamp + 1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs.meta b/Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs.meta new file mode 100644 index 000000000..b3cf32232 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Batching/UnbatcherTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ccc928bb22f5469886cef8c6132aa717 +timeCreated: 1623240730 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs b/Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs new file mode 100644 index 000000000..613b006c9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs @@ -0,0 +1,134 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class VirtualClientRpc : NetworkBehaviour + { + public event Action onVirtualSendInt; + + [ClientRpc] + public virtual void RpcSendInt(int someInt) => + onVirtualSendInt?.Invoke(someInt); + } + + class VirtualNoOverrideClientRpc : VirtualClientRpc {} + + class VirtualOverrideClientRpc : VirtualClientRpc + { + public event Action onOverrideSendInt; + + [ClientRpc] + public override void RpcSendInt(int someInt) => + onOverrideSendInt?.Invoke(someInt); + } + + class VirtualOverrideClientRpcWithBase : VirtualClientRpc + { + public event Action onOverrideSendInt; + + [ClientRpc] + public override void RpcSendInt(int someInt) + { + base.RpcSendInt(someInt); + onOverrideSendInt?.Invoke(someInt); + } + } + + public class ClientRpcOverrideTest : RemoteTestBase + { + [Test] + public void VirtualRpcIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualClientRpc hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.RpcSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + } + + [Test] + public void VirtualCommandWithNoOverrideIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualNoOverrideClientRpc hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.RpcSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualRpcIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideClientRpc hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.RpcSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(0)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualWithBaseCallsBothVirtualAndBase() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideClientRpcWithBase hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.RpcSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs.meta b/Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs.meta new file mode 100644 index 000000000..68e53470b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientRpcOverrideTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9759f0eddb9731c4b881ac30bacc0de0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientRpcTest.cs b/Assets/Mirror/Tests/Editor/ClientRpcTest.cs new file mode 100644 index 000000000..86e1e47c6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientRpcTest.cs @@ -0,0 +1,155 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class ClientRpcBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [ClientRpc] + public void SendInt(int someInt) => + onSendInt?.Invoke(someInt); + } + + class ExcludeOwnerBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [ClientRpc(includeOwner = false)] + public void RpcSendInt(int someInt) => + onSendInt?.Invoke(someInt); + } + + class AbstractNetworkBehaviourClientRpcBehaviour : NetworkBehaviour + { + public abstract class MockMonsterBase : NetworkBehaviour {} + public class MockZombie : MockMonsterBase {} + public class MockWolf : MockMonsterBase {} + + public event Action onSendMonsterBase; + + [ClientRpc] + public void RpcSendMonster(MockMonsterBase someMonster) => + onSendMonsterBase?.Invoke(someMonster); + } + + class RpcOverloads : NetworkBehaviour + { + public int firstCalled = 0; + public int secondCalled = 0; + + [ClientRpc] + public void RpcTest(int _) => ++firstCalled; + + [ClientRpc] + public void RpcTest(string _) => ++secondCalled; + } + + public class ClientRpcTest : RemoteTestBase + { + [Test] + public void RpcIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out ClientRpcBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int called = 0; + hostBehaviour.onSendInt += incomingInt => + { + called++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.SendInt(someInt); + ProcessMessages(); + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void RpcIsCalledForNotOwner() + { + // spawn without owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out ExcludeOwnerBehaviour hostBehaviour); + + const int someInt = 20; + + int called = 0; + hostBehaviour.onSendInt += incomingInt => + { + called++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.RpcSendInt(someInt); + ProcessMessages(); + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void RpcNotCalledForOwner() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out ExcludeOwnerBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int called = 0; + hostBehaviour.onSendInt += incomingInt => + { + called++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.RpcSendInt(someInt); + ProcessMessages(); + Assert.That(called, Is.EqualTo(0)); + } + + [Test] + public void RpcIsCalledWithAbstractNetworkBehaviourParameter() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out AbstractNetworkBehaviourClientRpcBehaviour hostBehaviour, NetworkServer.localConnection); + + // spawn clientrpc parameter targets + CreateNetworkedAndSpawn(out _, out _, out AbstractNetworkBehaviourClientRpcBehaviour.MockWolf wolf, NetworkServer.localConnection); + CreateNetworkedAndSpawn(out _, out _, out AbstractNetworkBehaviourClientRpcBehaviour.MockZombie zombie, NetworkServer.localConnection); + + AbstractNetworkBehaviourClientRpcBehaviour.MockMonsterBase currentMonster = null; + + int called = 0; + hostBehaviour.onSendMonsterBase += incomingMonster => + { + called++; + Assert.That(incomingMonster, Is.EqualTo(currentMonster)); + }; + + currentMonster = wolf; + hostBehaviour.RpcSendMonster(currentMonster); + ProcessMessages(); + Assert.That(called, Is.EqualTo(1)); + + currentMonster = zombie; + hostBehaviour.RpcSendMonster(currentMonster); + ProcessMessages(); + Assert.That(called, Is.EqualTo(2)); + } + + // RemoteCalls uses md.FullName which gives us the full command/rpc name + // like "System.Void Mirror.Tests.RemoteAttrributeTest.AuthorityBehaviour::SendInt(System.Int32)" + // which means overloads with same name but different types should work. + [Test] + public void RpcOverload() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcOverloads hostBehaviour, NetworkServer.localConnection); + + hostBehaviour.RpcTest(42); + hostBehaviour.RpcTest("A"); + ProcessMessages(); + Assert.That(hostBehaviour.firstCalled, Is.EqualTo(1)); + Assert.That(hostBehaviour.secondCalled, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientRpcTest.cs.meta b/Assets/Mirror/Tests/Editor/ClientRpcTest.cs.meta new file mode 100644 index 000000000..36ea9394d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientRpcTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2928376e56382b646975dd00b68c2287 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs new file mode 100644 index 000000000..1b6cdbc71 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs @@ -0,0 +1,58 @@ +using System; +using NUnit.Framework; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_ClearSpawners : ClientSceneTestsBase + { + [Test] + public void RemovesAllPrefabsFromDictionary() + { + NetworkClient.prefabs.Add(Guid.NewGuid(), null); + NetworkClient.prefabs.Add(Guid.NewGuid(), null); + + NetworkClient.ClearSpawners(); + Assert.IsEmpty(NetworkClient.prefabs); + } + + [Test] + public void RemovesAllSpawnHandlersFromDictionary() + { + NetworkClient.spawnHandlers.Add(Guid.NewGuid(), null); + NetworkClient.spawnHandlers.Add(Guid.NewGuid(), null); + + NetworkClient.ClearSpawners(); + Assert.IsEmpty(NetworkClient.spawnHandlers); + } + + [Test] + public void RemovesAllUnspawnHandlersFromDictionary() + { + NetworkClient.unspawnHandlers.Add(Guid.NewGuid(), null); + NetworkClient.unspawnHandlers.Add(Guid.NewGuid(), null); + + NetworkClient.ClearSpawners(); + + Assert.IsEmpty(NetworkClient.unspawnHandlers); + } + + [Test] + public void ClearsAllDictionary() + { + NetworkClient.prefabs.Add(Guid.NewGuid(), null); + NetworkClient.prefabs.Add(Guid.NewGuid(), null); + + NetworkClient.spawnHandlers.Add(Guid.NewGuid(), null); + NetworkClient.spawnHandlers.Add(Guid.NewGuid(), null); + + NetworkClient.unspawnHandlers.Add(Guid.NewGuid(), null); + NetworkClient.unspawnHandlers.Add(Guid.NewGuid(), null); + + NetworkClient.ClearSpawners(); + + Assert.IsEmpty(NetworkClient.prefabs); + Assert.IsEmpty(NetworkClient.spawnHandlers); + Assert.IsEmpty(NetworkClient.unspawnHandlers); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs.meta new file mode 100644 index 000000000..3a955eb07 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_ClearSpawners.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ad2fe5633a9652045a5f7eb2643b6956 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs new file mode 100644 index 000000000..9fb848785 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs @@ -0,0 +1,62 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_GetPrefab : ClientSceneTestsBase + { + [Test] + public void ReturnsFalseForEmptyGuid() + { + bool result = NetworkClient.GetPrefab(new Guid(), out GameObject prefab); + + Assert.IsFalse(result); + Assert.IsNull(prefab); + } + + [Test] + public void ReturnsFalseForPrefabNotFound() + { + Guid guid = Guid.NewGuid(); + bool result = NetworkClient.GetPrefab(guid, out GameObject prefab); + + Assert.IsFalse(result); + Assert.IsNull(prefab); + } + + [Test] + public void ReturnsFalseForPrefabIsNull() + { + Guid guid = Guid.NewGuid(); + NetworkClient.prefabs.Add(guid, null); + bool result = NetworkClient.GetPrefab(guid, out GameObject prefab); + + Assert.IsFalse(result); + Assert.IsNull(prefab); + } + + [Test] + public void ReturnsTrueWhenPrefabIsFound() + { + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + bool result = NetworkClient.GetPrefab(validPrefabGuid, out GameObject prefab); + + Assert.IsTrue(result); + Assert.NotNull(prefab); + } + + [Test] + public void HasOutPrefabWithCorrectGuid() + { + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + NetworkClient.GetPrefab(validPrefabGuid, out GameObject prefab); + + + Assert.NotNull(prefab); + + NetworkIdentity networkID = prefab.GetComponent(); + Assert.AreEqual(networkID.assetId, validPrefabGuid); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs.meta new file mode 100644 index 000000000..2dde78154 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_GetPrefab.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 27cbff3906928974bb20c3f50eacbbb0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs new file mode 100644 index 000000000..054151d94 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs @@ -0,0 +1,736 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.ClientSceneTests +{ + public class PayloadTestBehaviour : NetworkBehaviour + { + public int value; + public Vector3 direction; + + public event Action OnDeserializeCalled; + public event Action OnSerializeCalled; + + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + base.OnSerialize(writer, initialState); + + writer.WriteInt(value); + writer.WriteVector3(direction); + + OnSerializeCalled?.Invoke(); + + return true; + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + base.OnDeserialize(reader, initialState); + + value = reader.ReadInt(); + direction = reader.ReadVector3(); + + OnDeserializeCalled?.Invoke(); + } + } + + public class BehaviourWithEvents : NetworkBehaviour + { + public event Action OnStartAuthorityCalled; + public event Action OnStartClientCalled; + public event Action OnStartLocalPlayerCalled; + + public override void OnStartAuthority() + { + OnStartAuthorityCalled?.Invoke(); + } + public override void OnStartClient() + { + OnStartClientCalled?.Invoke(); + } + public override void OnStartLocalPlayer() + { + OnStartLocalPlayerCalled?.Invoke(); + } + } + + public class ClientSceneTests_OnSpawn : ClientSceneTestsBase + { + Dictionary spawned => NetworkClient.spawned; + + [TearDown] + public override void TearDown() + { + spawned.Clear(); + base.TearDown(); + } + + [Test] + public void FindOrSpawnObject_FindExistingObject() + { + CreateNetworked(out _, out NetworkIdentity existing); + const uint netId = 1000; + existing.netId = netId; + spawned.Add(netId, existing); + + SpawnMessage msg = new SpawnMessage + { + netId = netId + }; + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity found); + + Assert.IsTrue(success); + Assert.That(found, Is.EqualTo(existing)); + } + + [Test] + public void FindOrSpawnObject_ErrorWhenNoExistingAndAssetIdAndSceneIdAreBothEmpty() + { + const uint netId = 1001; + SpawnMessage msg = new SpawnMessage + { + assetId = new Guid(), + sceneId = 0, + netId = netId + }; + + LogAssert.Expect(LogType.Error, $"OnSpawn message with netId '{netId}' has no AssetId or sceneId"); + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsFalse(success); + Assert.IsNull(networkIdentity); + } + + [Test] + public void FindOrSpawnObject_SpawnsFromPrefabDictionary() + { + const uint netId = 1002; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid + + }; + + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsTrue(success); + Assert.IsNotNull(networkIdentity); + Assert.That(networkIdentity.name, Is.EqualTo($"{validPrefab.name}(Clone)")); + + // cleanup + GameObject.DestroyImmediate(networkIdentity.gameObject); + } + + [Test] + public void FindOrSpawnObject_ErrorWhenPrefabInNullInDictionary() + { + const uint netId = 1002; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid + }; + + // could happen if the prefab is destroyed or unloaded + NetworkClient.prefabs.Add(validPrefabGuid, null); + + LogAssert.Expect(LogType.Error, $"Failed to spawn server object, did you forget to add it to the NetworkManager? assetId={msg.assetId} netId={msg.netId}"); + LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}"); + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + + Assert.IsFalse(success); + Assert.IsNull(networkIdentity); + } + + [Test] + public void FindOrSpawnObject_SpawnsFromPrefabIfBothPrefabAndHandlerExists() + { + const uint netId = 1003; + int handlerCalled = 0; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid + }; + + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + NetworkClient.spawnHandlers.Add(validPrefabGuid, x => + { + handlerCalled++; + CreateNetworked(out GameObject go, out NetworkIdentity _); + return go; + }); + + + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsTrue(success); + Assert.IsNotNull(networkIdentity); + Assert.That(networkIdentity.name, Is.EqualTo($"{validPrefab.name}(Clone)")); + Assert.That(handlerCalled, Is.EqualTo(0), "Handler should not have been called"); + } + + [Test] + public void FindOrSpawnObject_SpawnHandlerCalledFromDictionary() + { + const uint netId = 1003; + int handlerCalled = 0; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid + }; + + GameObject createdInhandler = null; + + NetworkClient.spawnHandlers.Add(validPrefabGuid, x => + { + handlerCalled++; + Assert.That(x, Is.EqualTo(msg)); + CreateNetworked(out createdInhandler, out NetworkIdentity _); + return createdInhandler; + }); + + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsTrue(success); + Assert.IsNotNull(networkIdentity); + Assert.That(handlerCalled, Is.EqualTo(1)); + Assert.That(networkIdentity.gameObject, Is.EqualTo(createdInhandler), "Object returned should be the same object created by the spawn handler"); + } + + [Test] + public void FindOrSpawnObject_ErrorWhenSpawnHanlderReturnsNull() + { + const uint netId = 1003; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid + }; + + NetworkClient.spawnHandlers.Add(validPrefabGuid, (x) => null); + + LogAssert.Expect(LogType.Error, $"Spawn Handler returned null, Handler assetId '{msg.assetId}'"); + LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}"); + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsFalse(success); + Assert.IsNull(networkIdentity); + } + [Test] + public void FindOrSpawnObject_ErrorWhenSpawnHanlderReturnsWithoutNetworkIdentity() + { + const uint netId = 1003; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid + }; + + NetworkClient.spawnHandlers.Add(validPrefabGuid, (x) => + { + CreateGameObject(out GameObject go); + return go; + }); + + LogAssert.Expect(LogType.Error, $"Object Spawned by handler did not have a NetworkIdentity, Handler assetId '{validPrefabGuid}'"); + LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}"); + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsFalse(success); + Assert.IsNull(networkIdentity); + } + + NetworkIdentity CreateSceneObject(ulong sceneId) + { + CreateNetworked(out _, out NetworkIdentity identity); + // set sceneId to zero as it is set in onvalidate (does not set id at runtime) + identity.sceneId = sceneId; + NetworkClient.spawnableObjects.Add(sceneId, identity); + return identity; + } + + [Test] + public void FindOrSpawnObject_UsesSceneIdToSpawnFromSpawnableObjectsDictionary() + { + const uint netId = 1003; + const int sceneId = 100020; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + sceneId = sceneId + }; + + NetworkIdentity sceneObject = CreateSceneObject(sceneId); + + + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsTrue(success); + Assert.IsNotNull(networkIdentity); + Assert.That(networkIdentity, Is.EqualTo(sceneObject)); + } + + [Test] + public void FindOrSpawnObject_SpawnsUsingSceneIdInsteadOfAssetId() + { + const uint netId = 1003; + const int sceneId = 100020; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + sceneId = sceneId, + assetId = validPrefabGuid + }; + + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + NetworkIdentity sceneObject = CreateSceneObject(sceneId); + + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsTrue(success); + Assert.IsNotNull(networkIdentity); + Assert.That(networkIdentity, Is.EqualTo(sceneObject)); + } + + [Test] + public void FindOrSpawnObject_ErrorWhenSceneIdIsNotInSpawnableObjectsDictionary() + { + const uint netId = 1004; + const int sceneId = 100021; + SpawnMessage msg = new SpawnMessage + { + netId = netId, + sceneId = sceneId, + }; + + LogAssert.Expect(LogType.Error, $"Spawn scene object not found for {msg.sceneId:X}. Make sure that client and server use exactly the same project. This only happens if the hierarchy gets out of sync."); + LogAssert.Expect(LogType.Error, $"Could not spawn assetId={msg.assetId} scene={msg.sceneId:X} netId={msg.netId}"); + bool success = NetworkClient.FindOrSpawnObject(msg, out NetworkIdentity networkIdentity); + + Assert.IsFalse(success); + Assert.IsNull(networkIdentity); + } + + + [Test] + public void ApplyPayload_AppliesTransform() + { + const uint netId = 1000; + + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + Vector3 position = new Vector3(10, 0, 20); + Quaternion rotation = Quaternion.Euler(0, 45, 0); + Vector3 scale = new Vector3(1.5f, 1.5f, 1.5f); + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = false, + isOwner = false, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = position, + rotation = rotation, + scale = scale, + + payload = default, + }; + + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.That(identity.transform.position, Is.EqualTo(position)); + // use angle because of floating point numbers + // only need to check if rotations are approximately equal + Assert.That(Quaternion.Angle(identity.transform.rotation, rotation), Is.LessThan(0.0001f)); + Assert.That(identity.transform.localScale, Is.EqualTo(scale)); + } + + [Test] + public void ApplyPayload_AppliesLocalValuesToTransform() + { + const uint netId = 1000; + CreateGameObject(out GameObject parent); + parent.transform.position = new Vector3(100, 20, 0); + parent.transform.rotation = Quaternion.LookRotation(Vector3.left); + parent.transform.localScale = Vector3.one * 2; + + CreateNetworked(out GameObject go, out NetworkIdentity identity); + go.transform.parent = parent.transform; + + Vector3 position = new Vector3(10, 0, 20); + Quaternion rotation = Quaternion.Euler(0, 45, 0); + Vector3 scale = new Vector3(1.5f, 1.5f, 1.5f); + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = false, + isOwner = false, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = position, + rotation = rotation, + scale = scale, + + payload = default, + }; + + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.That(identity.transform.localPosition, Is.EqualTo(position)); + // use angle because of floating point numbers + // only need to check if rotations are approximately equal + Assert.That(Quaternion.Angle(identity.transform.localRotation, rotation), Is.LessThan(0.0001f)); + Assert.That(identity.transform.localScale, Is.EqualTo(scale)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ApplyPayload_AppliesAuthority(bool isOwner) + { + const uint netId = 1000; + + CreateNetworked(out _, out NetworkIdentity identity); + + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = false, + isOwner = isOwner, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = Vector3.zero, + rotation = Quaternion.identity, + scale = Vector3.one, + + payload = default + }; + + // set to opposite to make sure it is changed + identity.hasAuthority = !isOwner; + + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.That(identity.hasAuthority, Is.EqualTo(isOwner)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ApplyPayload_EnablesObject(bool startActive) + { + const uint netId = 1000; + + CreateNetworked(out GameObject go, out NetworkIdentity identity); + go.SetActive(startActive); + + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = false, + isOwner = false, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = Vector3.zero, + rotation = Quaternion.identity, + scale = Vector3.one, + + payload = default, + }; + + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.IsTrue(identity.gameObject.activeSelf); + } + + [Test] + public void ApplyPayload_SetsAssetId() + { + const uint netId = 1000; + + CreateNetworked(out _, out NetworkIdentity identity); + + Guid guid = Guid.NewGuid(); + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = false, + isOwner = false, + sceneId = 0, + assetId = guid, + // use local values for VR support + position = Vector3.zero, + rotation = Quaternion.identity, + scale = Vector3.one, + + payload = default + }; + + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.IsTrue(identity.gameObject.activeSelf); + + Assert.That(identity.assetId, Is.EqualTo(guid)); + } + + [Test] + public void ApplyPayload_DoesNotSetAssetIdToEmpty() + { + const uint netId = 1000; + + CreateNetworked(out _, out NetworkIdentity identity); + Guid guid = Guid.NewGuid(); + identity.assetId = guid; + + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = false, + isOwner = false, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = Vector3.zero, + rotation = Quaternion.identity, + scale = Vector3.one, + + payload = default + }; + + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.That(identity.assetId, Is.EqualTo(guid), "AssetId should not have changed"); + } + + [Test] + public void ApplyPayload_SendsDataToNetworkBehaviourDeserialize() + { + const int value = 12; + Vector3 direction = new Vector3(0, 1, 1); + + const uint netId = 1000; + + // server object + CreateNetworked(out _, out NetworkIdentity serverIdentity, out PayloadTestBehaviour serverPayloadBehaviour); + + // client object + CreateNetworked(out _, out NetworkIdentity clientIdentity, out PayloadTestBehaviour clientPayloadBehaviour); + + int onSerializeCalled = 0; + serverPayloadBehaviour.OnSerializeCalled += () => { onSerializeCalled++; }; + + int onDeserializeCalled = 0; + clientPayloadBehaviour.OnDeserializeCalled += () => { onDeserializeCalled++; }; + + serverPayloadBehaviour.value = value; + serverPayloadBehaviour.direction = direction; + + NetworkWriter ownerWriter = new NetworkWriter(); + NetworkWriter observersWriter = new NetworkWriter(); + serverIdentity.OnSerializeAllSafely(true, ownerWriter, observersWriter); + + // check that Serialize was called + Assert.That(onSerializeCalled, Is.EqualTo(1)); + + // create spawn message + SpawnMessage msg = new SpawnMessage + { + netId = netId, + payload = ownerWriter.ToArraySegment(), + }; + + // check values start default + Assert.That(onDeserializeCalled, Is.EqualTo(0)); + Assert.That(clientPayloadBehaviour.value, Is.EqualTo(0)); + Assert.That(clientPayloadBehaviour.direction, Is.EqualTo(Vector3.zero)); + + NetworkClient.ApplySpawnPayload(clientIdentity, msg); + + // check values have been set by payload + Assert.That(onDeserializeCalled, Is.EqualTo(1)); + Assert.That(clientPayloadBehaviour.value, Is.EqualTo(value)); + Assert.That(clientPayloadBehaviour.direction, Is.EqualTo(direction)); + } + + [Test] + public void ApplyPayload_LocalPlayerAddsIdentityToConnection() + { + Debug.Assert(NetworkClient.localPlayer == null, "LocalPlayer should be null before this test"); + const uint netId = 1000; + + CreateNetworked(out _, out NetworkIdentity identity); + + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = true, + isOwner = true, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = Vector3.zero, + rotation = Quaternion.identity, + scale = Vector3.one, + + payload = default, + }; + + NetworkClient.connection = new FakeNetworkConnection(); + NetworkClient.ready = true; + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.That(NetworkClient.localPlayer, Is.EqualTo(identity)); + Assert.That(NetworkClient.connection.identity, Is.EqualTo(identity)); + } + + [Test] + public void ApplyPayload_LocalPlayerWarningWhenNoReadyConnection() + { + Debug.Assert(NetworkClient.localPlayer == null, "LocalPlayer should be null before this test"); + const uint netId = 1000; + + CreateNetworked(out _, out NetworkIdentity identity); + + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = true, + isOwner = true, + sceneId = 0, + assetId = Guid.Empty, + // use local values for VR support + position = Vector3.zero, + rotation = Quaternion.identity, + scale = Vector3.one, + + payload = default, + }; + + + LogAssert.Expect(LogType.Warning, "No ready connection found for setting player controller during InternalAddPlayer"); + NetworkClient.ApplySpawnPayload(identity, msg); + + Assert.That(NetworkClient.localPlayer, Is.EqualTo(identity)); + } + + [Flags] + public enum SpawnFinishedState + { + isSpawnFinished = 1, + hasAuthority = 2, + isLocalPlayer = 4 + } + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(2)] + [TestCase(3)] + [TestCase(4)] + [TestCase(5)] + [TestCase(6)] + [TestCase(7)] + public void ApplyPayload_isSpawnFinished(SpawnFinishedState flag) + { + bool isSpawnFinished = flag.HasFlag(SpawnFinishedState.isSpawnFinished); + bool hasAuthority = flag.HasFlag(SpawnFinishedState.hasAuthority); + bool isLocalPlayer = flag.HasFlag(SpawnFinishedState.isLocalPlayer); + + if (isSpawnFinished) + { + NetworkClient.OnObjectSpawnFinished(new ObjectSpawnFinishedMessage()); + } + + const uint netId = 1000; + CreateNetworked(out _, out NetworkIdentity identity, out BehaviourWithEvents events); + + int onStartAuthorityCalled = 0; + int onStartClientCalled = 0; + int onStartLocalPlayerCalled = 0; + events.OnStartAuthorityCalled += () => { onStartAuthorityCalled++; }; + events.OnStartClientCalled += () => { onStartClientCalled++; }; + events.OnStartLocalPlayerCalled += () => { onStartLocalPlayerCalled++; }; + + SpawnMessage msg = new SpawnMessage + { + netId = netId, + isLocalPlayer = isLocalPlayer, + isOwner = hasAuthority, + }; + + NetworkClient.ApplySpawnPayload(identity, msg); + + if (isSpawnFinished) + { + Assert.That(onStartClientCalled, Is.EqualTo(1)); + Assert.That(onStartAuthorityCalled, Is.EqualTo(hasAuthority ? 1 : 0)); + Assert.That(onStartLocalPlayerCalled, Is.EqualTo(isLocalPlayer ? 1 : 0)); + } + else + { + Assert.That(onStartAuthorityCalled, Is.Zero); + Assert.That(onStartClientCalled, Is.Zero); + Assert.That(onStartLocalPlayerCalled, Is.Zero); + } + } + + [Test] + public void OnSpawn_SpawnsAndAppliesPayload() + { + const int netId = 1; + Debug.Assert(spawned.Count == 0, "There should be no spawned objects before test"); + + Vector3 position = new Vector3(30, 20, 10); + Quaternion rotation = Quaternion.Euler(0, 0, 90); + SpawnMessage msg = new SpawnMessage + { + netId = netId, + assetId = validPrefabGuid, + position = position, + rotation = rotation + }; + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + + NetworkClient.OnSpawn(msg); + + Assert.That(spawned.Count, Is.EqualTo(1)); + Assert.IsTrue(spawned.ContainsKey(netId)); + + NetworkIdentity identity = spawned[netId]; + Assert.IsNotNull(identity); + Assert.That(identity.name, Is.EqualTo($"{validPrefab.name}(Clone)")); + Assert.That(identity.transform.position, Is.EqualTo(position)); + // use angle because of floating point numbers + // only need to check if rotations are approximately equal + Assert.That(Quaternion.Angle(identity.transform.rotation, rotation), Is.LessThan(0.0001f)); + } + + [Test] + public void OnSpawn_GiveNoExtraErrorsWhenPrefabIsntSpawned() + { + const int netId = 20033; + Debug.Assert(spawned.Count == 0, "There should be no spawned objects before test"); + SpawnMessage msg = new SpawnMessage + { + netId = netId, + }; + + // Check for log that FindOrSpawnObject gives, and make sure there are no other error logs + LogAssert.Expect(LogType.Error, $"OnSpawn message with netId '{netId}' has no AssetId or sceneId"); + NetworkClient.OnSpawn(msg); + + Assert.That(spawned, Is.Empty); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs.meta new file mode 100644 index 000000000..03d445c2d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_OnSpawn.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b85d949dccc6ab4498da187264323dcc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs new file mode 100644 index 000000000..4f9532c28 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs @@ -0,0 +1,104 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_PrepareToSpawnSceneObjects : ClientSceneTestsBase + { + NetworkIdentity CreateSceneObject(ulong sceneId) + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity); + gameObject.name = "Runtime GameObject"; + // set sceneId to zero as it is set in onvalidate (does not set id at runtime) + identity.sceneId = sceneId; + return identity; + } + + [Test] + public void AddsAllInactiveIdentitiesInSceneWithSceneIdToDictionary() + { + NetworkIdentity obj1 = CreateSceneObject(10); + NetworkIdentity obj2 = CreateSceneObject(11); + + obj1.gameObject.SetActive(false); + obj2.gameObject.SetActive(false); + + NetworkClient.PrepareToSpawnSceneObjects(); + + Assert.That(NetworkClient.spawnableObjects, Has.Count.EqualTo(2)); + + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsValue(obj1)); + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsValue(obj2)); + } + + [Test] + public void DoesNotAddActiveObjectsToDictionary() + { + NetworkIdentity active = CreateSceneObject(30); + NetworkIdentity inactive = CreateSceneObject(32); + + active.gameObject.SetActive(true); + inactive.gameObject.SetActive(false); + + NetworkClient.PrepareToSpawnSceneObjects(); + + Assert.That(NetworkClient.spawnableObjects, Has.Count.EqualTo(1)); + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsValue(inactive)); + Assert.IsFalse(NetworkClient.spawnableObjects.ContainsValue(active)); + } + + [Test] + public void DoesNotAddObjectsWithNoSceneId() + { + NetworkIdentity noId = CreateSceneObject(0); + NetworkIdentity hasId = CreateSceneObject(40); + + noId.gameObject.SetActive(false); + hasId.gameObject.SetActive(false); + + NetworkClient.PrepareToSpawnSceneObjects(); + + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsValue(hasId)); + Assert.IsFalse(NetworkClient.spawnableObjects.ContainsValue(noId)); + } + + [Test] + public void AddsIdentitiesToDictionaryUsingSceneId() + { + NetworkIdentity obj1 = CreateSceneObject(20); + NetworkIdentity obj2 = CreateSceneObject(21); + obj1.gameObject.SetActive(false); + obj2.gameObject.SetActive(false); + + NetworkClient.PrepareToSpawnSceneObjects(); + + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsKey(20)); + Assert.That(NetworkClient.spawnableObjects[20], Is.EqualTo(obj1)); + + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsKey(21)); + Assert.That(NetworkClient.spawnableObjects[21], Is.EqualTo(obj2)); + } + + [Test] + public void ClearsExistingItemsFromDictionary() + { + // destroyed objects from old scene + NetworkClient.spawnableObjects.Add(60, null); + NetworkClient.spawnableObjects.Add(62, null); + + // active object + NetworkIdentity obj1 = CreateSceneObject(61); + NetworkClient.spawnableObjects.Add(61, obj1); + + // new disabled object + NetworkIdentity obj2 = CreateSceneObject(63); + obj2.gameObject.SetActive(false); + + NetworkClient.PrepareToSpawnSceneObjects(); + + Assert.That(NetworkClient.spawnableObjects, Has.Count.EqualTo(1)); + Assert.IsFalse(NetworkClient.spawnableObjects.ContainsValue(null)); + Assert.IsTrue(NetworkClient.spawnableObjects.ContainsValue(obj2)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs.meta new file mode 100644 index 000000000..9652845b1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_PrepareToSpawnSceneObjects.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 546931b1b1c38d5498f2c189e68b63aa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs new file mode 100644 index 000000000..a63b1f628 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs @@ -0,0 +1,300 @@ +using System; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_RegisterPrefab : ClientSceneTests_RegisterPrefabBase + { + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + public void Prefab_AddsPrefabToDictionary(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + + CallRegisterPrefab(validPrefab, overload); + + Assert.IsTrue(NetworkClient.prefabs.ContainsKey(guid)); + Assert.AreEqual(NetworkClient.prefabs[guid], validPrefab); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId)] + public void PrefabNewGuid_ErrorDoesNotChangePrefabsAssetId(RegisterPrefabOverload overload) + { + Guid guid = anotherGuid; + + LogAssert.Expect(LogType.Error, $"Could not register '{validPrefab.name}' to {guid} because it already had an AssetId, Existing assetId {validPrefabGuid}"); + CallRegisterPrefab(validPrefab, overload); + + Assert.IsFalse(NetworkClient.prefabs.ContainsKey(guid)); + + NetworkIdentity netId = validPrefab.GetComponent(); + + Assert.AreEqual(netId.assetId, validPrefabGuid); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void HandlerNewGuid_ErrorDoesNotChangePrefabsAssetId(RegisterPrefabOverload overload) + { + Guid guid = anotherGuid; + + LogAssert.Expect(LogType.Error, $"Could not register Handler for '{validPrefab.name}' to {guid} because it already had an AssetId, Existing assetId {validPrefabGuid}"); + CallRegisterPrefab(validPrefab, overload); + + Assert.IsFalse(NetworkClient.spawnHandlers.ContainsKey(guid)); + Assert.IsFalse(NetworkClient.unspawnHandlers.ContainsKey(guid)); + + NetworkIdentity netId = validPrefab.GetComponent(); + + Assert.AreEqual(netId.assetId, validPrefabGuid); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId)] + public void PrefabNewGuid_NoErrorWhenNewAssetIdIsSameAsCurrentPrefab(RegisterPrefabOverload overload) + { + Guid guid = validPrefabGuid; + + CallRegisterPrefab(validPrefab, overload, guid); + + Assert.IsTrue(NetworkClient.prefabs.ContainsKey(guid)); + + NetworkIdentity netId = validPrefab.GetComponent(); + + Assert.AreEqual(netId.assetId, validPrefabGuid); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void HandlerNewGuid_NoErrorWhenAssetIdIsSameAsCurrentPrefab(RegisterPrefabOverload overload) + { + Guid guid = validPrefabGuid; + + CallRegisterPrefab(validPrefab, overload, guid); + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + Assert.IsTrue(NetworkClient.unspawnHandlers.ContainsKey(guid)); + + NetworkIdentity netId = validPrefab.GetComponent(); + + Assert.AreEqual(netId.assetId, validPrefabGuid); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void ErrorForNullPrefab(RegisterPrefabOverload overload) + { + string msg = OverloadWithHandler(overload) + ? "Could not register handler for prefab because the prefab was null" + : "Could not register prefab because it was null"; + + LogAssert.Expect(LogType.Error, msg); + CallRegisterPrefab(null, overload); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void ErrorForPrefabWithoutNetworkIdentity(RegisterPrefabOverload overload) + { + string msg = OverloadWithHandler(overload) + ? $"Could not register handler for '{invalidPrefab.name}' since it contains no NetworkIdentity component" + : $"Could not register '{invalidPrefab.name}' since it contains no NetworkIdentity component"; + + LogAssert.Expect(LogType.Error, msg); + CallRegisterPrefab(invalidPrefab, overload); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void NewGuid_ErrorForEmptyGuid(RegisterPrefabOverload overload) + { + string msg = OverloadWithHandler(overload) + ? $"Could not register handler for '{validPrefab.name}' with new assetId because the new assetId was empty" + : $"Could not register '{validPrefab.name}' with new assetId because the new assetId was empty"; + LogAssert.Expect(LogType.Error, msg); + CallRegisterPrefab(validPrefab, overload, new Guid()); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void ErrorIfPrefabHadSceneId(RegisterPrefabOverload overload) + { + GameObject clone = GameObject.Instantiate(validPrefab); + NetworkIdentity netId = clone.GetComponent(); + // Scene Id needs to not be zero for this test + netId.sceneId = 20; + + LogAssert.Expect(LogType.Error, $"Can not Register '{clone.name}' because it has a sceneId, make sure you are passing in the original prefab and not an instance in the scene."); + CallRegisterPrefab(clone, overload); + + GameObject.DestroyImmediate(clone); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void ErrorForNetworkIdentityInChildren(RegisterPrefabOverload overload) + { + LogAssert.Expect(LogType.Error, $"Prefab '{prefabWithChildren.name}' has multiple NetworkIdentity components. There should only be one NetworkIdentity on a prefab, and it must be on the root object."); + CallRegisterPrefab(prefabWithChildren, overload); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + public void Prefab_WarningForAssetIdAlreadyExistingInPrefabsDictionary(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + + NetworkClient.prefabs.Add(guid, validPrefab); + + LogAssert.Expect(LogType.Warning, $"Replacing existing prefab with assetId '{guid}'. Old prefab '{validPrefab.name}', New prefab '{validPrefab.name}'"); + CallRegisterPrefab(validPrefab, overload); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void Handler_ErrorForAssetIdAlreadyExistingInPrefabsDictionary(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + + NetworkClient.prefabs.Add(guid, validPrefab); + + LogAssert.Expect(LogType.Error, $"assetId '{guid}' is already used by prefab '{validPrefab.name}', unregister the prefab first before trying to add handler"); + CallRegisterPrefab(validPrefab, overload); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void WarningForAssetIdAlreadyExistingInHandlersDictionary(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + + NetworkClient.spawnHandlers.Add(guid, x => null); + NetworkClient.unspawnHandlers.Add(guid, x => {}); + + string msg = OverloadWithHandler(overload) + ? $"Replacing existing spawnHandlers for prefab '{validPrefab.name}' with assetId '{guid}'" + : $"Adding prefab '{validPrefab.name}' with assetId '{guid}' when spawnHandlers with same assetId already exists."; + + LogAssert.Expect(LogType.Warning, msg); + CallRegisterPrefab(validPrefab, overload); + } + + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + public void SpawnDelegate_AddsHandlerToSpawnHandlers(RegisterPrefabOverload overload) + { + int handlerCalled = 0; + + Guid guid = GuidForOverload(overload); + SpawnDelegate handler = new SpawnDelegate((pos, rot) => + { + handlerCalled++; + return null; + }); + + CallRegisterPrefab(validPrefab, overload, handler); + + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + + // check spawnHandler above is called + SpawnHandlerDelegate handlerInDictionary = NetworkClient.spawnHandlers[guid]; + handlerInDictionary.Invoke(default); + Assert.That(handlerCalled, Is.EqualTo(1)); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + public void SpawnDelegate_AddsHandlerToSpawnHandlersWithCorrectArguments(RegisterPrefabOverload overload) + { + int handlerCalled = 0; + Vector3 somePosition = new Vector3(10, 20, 3); + + Guid guid = GuidForOverload(overload); + SpawnDelegate handler = new SpawnDelegate((pos, assetId) => + { + handlerCalled++; + Assert.That(pos, Is.EqualTo(somePosition)); + Assert.That(assetId, Is.EqualTo(guid)); + return null; + }); + + CallRegisterPrefab(validPrefab, overload, handler); + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + + // check spawnHandler above is called + SpawnHandlerDelegate handlerInDictionary = NetworkClient.spawnHandlers[guid]; + handlerInDictionary.Invoke(new SpawnMessage { position = somePosition, assetId = guid }); + Assert.That(handlerCalled, Is.EqualTo(1)); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + public void SpawnDelegate_ErrorWhenSpawnHandlerIsNull(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + LogAssert.Expect(LogType.Error, $"Can not Register null SpawnHandler for {guid}"); + CallRegisterPrefab(validPrefab, overload, spawnHandler: null); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void SpawnHandleDelegate_AddsHandlerToSpawnHandlers(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + + SpawnHandlerDelegate handler = new SpawnHandlerDelegate(x => null); + + CallRegisterPrefab(validPrefab, overload, handler); + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + Assert.AreEqual(NetworkClient.spawnHandlers[guid], handler); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void SpawnHandleDelegate_ErrorWhenSpawnHandlerIsNull(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + LogAssert.Expect(LogType.Error, $"Can not Register null SpawnHandler for {guid}"); + CallRegisterPrefab(validPrefab, overload, spawnHandlerDelegate: null); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void Handler_ErrorWhenUnSpawnHandlerIsNull(RegisterPrefabOverload overload) + { + Guid guid = GuidForOverload(overload); + LogAssert.Expect(LogType.Error, $"Can not Register null UnSpawnHandler for {guid}"); + CallRegisterPrefab(validPrefab, overload, unspawnHandler: null); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs.meta new file mode 100644 index 000000000..803aa0ab5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterPrefab.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bdfdcfafb85b3be418f2085e38663006 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs new file mode 100644 index 000000000..fce157b0d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs @@ -0,0 +1,224 @@ +using System; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_RegisterSpawnHandler : ClientSceneTestsBase + { + [Test] + public void SpawnDelegate_AddsHandlerToSpawnHandlers() + { + int handlerCalled = 0; + + Guid guid = Guid.NewGuid(); + SpawnDelegate spawnHandler = new SpawnDelegate((pos, rot) => + { + handlerCalled++; + return null; + }); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + + // check spawnHandler above is called + SpawnHandlerDelegate handler = NetworkClient.spawnHandlers[guid]; + handler.Invoke(default); + Assert.That(handlerCalled, Is.EqualTo(1)); + } + + [Test] + public void SpawnDelegate_AddsHandlerToSpawnHandlersWithCorrectArguments() + { + int handlerCalled = 0; + Vector3 somePosition = new Vector3(10, 20, 3); + + Guid guid = Guid.NewGuid(); + SpawnDelegate spawnHandler = new SpawnDelegate((pos, assetId) => + { + handlerCalled++; + Assert.That(pos, Is.EqualTo(somePosition)); + Assert.That(assetId, Is.EqualTo(guid)); + return null; + }); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + + // check spawnHandler above is called + SpawnHandlerDelegate handler = NetworkClient.spawnHandlers[guid]; + handler.Invoke(new SpawnMessage { position = somePosition, assetId = guid }); + Assert.That(handlerCalled, Is.EqualTo(1)); + } + + [Test] + public void SpawnDelegate_AddsHandlerToUnSpawnHandlers() + { + Guid guid = Guid.NewGuid(); + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + Assert.IsTrue(NetworkClient.unspawnHandlers.ContainsKey(guid)); + Assert.AreEqual(NetworkClient.unspawnHandlers[guid], unspawnHandler); + } + + [Test] + public void SpawnDelegate_ErrorWhenSpawnHandlerIsNull() + { + Guid guid = Guid.NewGuid(); + SpawnDelegate spawnHandler = null; + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + LogAssert.Expect(LogType.Error, $"Can not Register null SpawnHandler for {guid}"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + [Test] + public void SpawnDelegate_ErrorWhenUnSpawnHandlerIsNull() + { + Guid guid = Guid.NewGuid(); + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + UnSpawnDelegate unspawnHandler = null; + + LogAssert.Expect(LogType.Error, $"Can not Register null UnSpawnHandler for {guid}"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + [Test] + public void SpawnDelegate_ErrorWhenAssetIdIsEmpty() + { + Guid guid = new Guid(); + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + LogAssert.Expect(LogType.Error, "Can not Register SpawnHandler for empty Guid"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + [Test] + public void SpawnDelegate_WarningWhenHandlerForGuidAlreadyExistsInHandlerDictionary() + { + Guid guid = Guid.NewGuid(); + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + SpawnDelegate spawnHandler2 = new SpawnDelegate((x, y) => new GameObject()); + UnSpawnDelegate unspawnHandler2 = new UnSpawnDelegate(x => UnityEngine.Object.Destroy(x)); + + LogAssert.Expect(LogType.Warning, $"Replacing existing spawnHandlers for {guid}"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler2, unspawnHandler2); + } + + [Test] + public void SpawnDelegate_ErrorWhenHandlerForGuidAlreadyExistsInPrefabDictionary() + { + Guid guid = Guid.NewGuid(); + NetworkClient.prefabs.Add(guid, validPrefab); + + SpawnDelegate spawnHandler = new SpawnDelegate((x, y) => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + LogAssert.Expect(LogType.Error, $"assetId '{guid}' is already used by prefab '{validPrefab.name}'"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + + [Test] + public void SpawnHandlerDelegate_AddsHandlerToSpawnHandlers() + { + Guid guid = Guid.NewGuid(); + SpawnHandlerDelegate spawnHandler = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(guid)); + Assert.AreEqual(NetworkClient.spawnHandlers[guid], spawnHandler); + } + + [Test] + public void SpawnHandlerDelegate_AddsHandlerToUnSpawnHandlers() + { + Guid guid = Guid.NewGuid(); + SpawnHandlerDelegate spawnHandler = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + Assert.IsTrue(NetworkClient.unspawnHandlers.ContainsKey(guid)); + Assert.AreEqual(NetworkClient.unspawnHandlers[guid], unspawnHandler); + } + + [Test] + public void SpawnHandlerDelegate_ErrorWhenSpawnHandlerIsNull() + { + Guid guid = Guid.NewGuid(); + SpawnHandlerDelegate spawnHandler = null; + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + LogAssert.Expect(LogType.Error, $"Can not Register null SpawnHandler for {guid}"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + [Test] + public void SpawnHandlerDelegate_ErrorWhenUnSpawnHandlerIsNull() + { + Guid guid = Guid.NewGuid(); + SpawnHandlerDelegate spawnHandler = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = null; + + LogAssert.Expect(LogType.Error, $"Can not Register null UnSpawnHandler for {guid}"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + [Test] + public void SpawnHandlerDelegate_ErrorWhenAssetIdIsEmpty() + { + Guid guid = new Guid(); + SpawnHandlerDelegate spawnHandler = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + LogAssert.Expect(LogType.Error, "Can not Register SpawnHandler for empty Guid"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + [Test] + public void SpawnHandlerDelegate_WarningWhenHandlerForGuidAlreadyExistsInHandlerDictionary() + { + Guid guid = Guid.NewGuid(); + SpawnHandlerDelegate spawnHandler = new SpawnHandlerDelegate(x => null); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => {}); + + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + + SpawnHandlerDelegate spawnHandler2 = new SpawnHandlerDelegate(x => new GameObject()); + UnSpawnDelegate unspawnHandler2 = new UnSpawnDelegate(x => UnityEngine.Object.Destroy(x)); + + LogAssert.Expect(LogType.Warning, $"Replacing existing spawnHandlers for {guid}"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler2, unspawnHandler2); + } + + [Test] + public void SpawnHandlerDelegate_ErrorWhenHandlerForGuidAlreadyExistsInPrefabDictionary() + { + Guid guid = Guid.NewGuid(); + NetworkClient.prefabs.Add(guid, validPrefab); + + SpawnHandlerDelegate spawnHandler = new SpawnHandlerDelegate(x => new GameObject()); + UnSpawnDelegate unspawnHandler = new UnSpawnDelegate(x => UnityEngine.Object.Destroy(x)); + + LogAssert.Expect(LogType.Error, $"assetId '{guid}' is already used by prefab '{validPrefab.name}'"); + NetworkClient.RegisterSpawnHandler(guid, spawnHandler, unspawnHandler); + } + + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs.meta new file mode 100644 index 000000000..ec826b7c5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_RegisterSpawnHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c7e6577e8c2e64e41ae255edc61e91a2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs new file mode 100644 index 000000000..ec1b7a6a5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs @@ -0,0 +1,48 @@ +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_UnregisterPrefab : ClientSceneTestsBase + { + [Test] + public void RemovesPrefabFromDictionary() + { + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + NetworkClient.UnregisterPrefab(validPrefab); + Assert.IsFalse(NetworkClient.prefabs.ContainsKey(validPrefabGuid)); + } + + [Test] + public void RemovesSpawnHandlerFromDictionary() + { + NetworkClient.spawnHandlers.Add(validPrefabGuid, new SpawnHandlerDelegate(x => null)); + NetworkClient.UnregisterPrefab(validPrefab); + Assert.IsFalse(NetworkClient.spawnHandlers.ContainsKey(validPrefabGuid)); + } + + [Test] + public void RemovesUnSpawnHandlerFromDictionary() + { + NetworkClient.unspawnHandlers.Add(validPrefabGuid, new UnSpawnDelegate(x => {})); + NetworkClient.UnregisterPrefab(validPrefab); + Assert.IsFalse(NetworkClient.unspawnHandlers.ContainsKey(validPrefabGuid)); + } + + [Test] + public void ErrorWhenPrefabIsNull() + { + LogAssert.Expect(LogType.Error, "Could not unregister prefab because it was null"); + NetworkClient.UnregisterPrefab(null); + } + + [Test] + public void ErrorWhenPrefabHasNoNetworkIdentity() + { + LogAssert.Expect(LogType.Error, $"Could not unregister '{invalidPrefab.name}' since it contains no NetworkIdentity component"); + NetworkClient.UnregisterPrefab(invalidPrefab); + } + + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs.meta new file mode 100644 index 000000000..8478bffbc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterPrefab.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: aa6fa692d80eaf8419e559c35034f016 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs new file mode 100644 index 000000000..7e1f6e3d3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs @@ -0,0 +1,33 @@ +using NUnit.Framework; + +namespace Mirror.Tests.ClientSceneTests +{ + public class ClientSceneTests_UnregisterSpawnHandler : ClientSceneTestsBase + { + [Test] + public void RemovesSpawnHandlersFromDictionary() + { + NetworkClient.spawnHandlers.Add(validPrefabGuid, new SpawnHandlerDelegate(x => null)); + NetworkClient.UnregisterSpawnHandler(validPrefabGuid); + Assert.IsFalse(NetworkClient.unspawnHandlers.ContainsKey(validPrefabGuid)); + } + + [Test] + public void RemovesUnSpawnHandlersFromDictionary() + { + NetworkClient.unspawnHandlers.Add(validPrefabGuid, new UnSpawnDelegate(x => {})); + NetworkClient.UnregisterSpawnHandler(validPrefabGuid); + Assert.IsFalse(NetworkClient.unspawnHandlers.ContainsKey(validPrefabGuid)); + } + + [Test] + public void DoesNotRemovePrefabDictionary() + { + NetworkClient.prefabs.Add(validPrefabGuid, validPrefab); + NetworkClient.UnregisterSpawnHandler(validPrefabGuid); + // Should not be removed + Assert.IsTrue(NetworkClient.prefabs.ContainsKey(validPrefabGuid)); + } + + } +} diff --git a/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs.meta b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs.meta new file mode 100644 index 000000000..006dad00a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ClientSceneTests_UnregisterSpawnHandler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49ef76b8883ba3845942503683c9a9b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/CommandOverrideTest.cs b/Assets/Mirror/Tests/Editor/CommandOverrideTest.cs new file mode 100644 index 000000000..c9c57d54e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CommandOverrideTest.cs @@ -0,0 +1,182 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class VirtualCommand : NetworkBehaviour + { + public event Action onVirtualSendInt; + + [Command] + public virtual void CmdSendInt(int someInt) => + onVirtualSendInt?.Invoke(someInt); + } + + class VirtualNoOverrideCommand : VirtualCommand {} + + class VirtualOverrideCommand : VirtualCommand + { + public event Action onOverrideSendInt; + + [Command] + public override void CmdSendInt(int someInt) => + onOverrideSendInt?.Invoke(someInt); + } + + class VirtualOverrideCommandWithBase : VirtualCommand + { + public event Action onOverrideSendInt; + + [Command] + public override void CmdSendInt(int someInt) + { + base.CmdSendInt(someInt); + onOverrideSendInt?.Invoke(someInt); + } + } + + // test for 2 overrides + class VirtualOverrideCommandWithBase2 : VirtualOverrideCommandWithBase + { + public event Action onOverrideSendInt2; + + [Command] + public override void CmdSendInt(int someInt) + { + base.CmdSendInt(someInt); + onOverrideSendInt2?.Invoke(someInt); + } + } + + public class CommandOverrideTest : RemoteTestBase + { + [Test] + public void VirtualCommandIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualCommand hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + } + + [Test] + public void VirtualCommandWithNoOverrideIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualNoOverrideCommand hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualCommandIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideCommand hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(0)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualWithBaseCallsBothVirtualAndBase() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideCommandWithBase hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualWithBaseCallsAllMethodsThatCallBase() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideCommandWithBase2 hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + int override2CallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.onOverrideSendInt2 += incomingInt => + { + override2CallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + Assert.That(override2CallCount, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/CommandOverrideTest.cs.meta b/Assets/Mirror/Tests/Editor/CommandOverrideTest.cs.meta new file mode 100644 index 000000000..2afb7834e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CommandOverrideTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 881c801c26e90df43b3558a23c96e0ea +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/CommandTest.cs b/Assets/Mirror/Tests/Editor/CommandTest.cs new file mode 100644 index 000000000..3c6b4b884 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CommandTest.cs @@ -0,0 +1,247 @@ +using System; +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class AuthorityBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [Command] + public void SendInt(int someInt) => + onSendInt?.Invoke(someInt); + } + + class IgnoreAuthorityBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [Command(requiresAuthority = false)] + public void CmdSendInt(int someInt) => + onSendInt?.Invoke(someInt); + } + + class SenderConnectionBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [Command] + public void CmdSendInt(int someInt, NetworkConnectionToClient conn = null) => + onSendInt?.Invoke(someInt, conn); + } + + class SenderConnectionIgnoreAuthorityBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [Command(requiresAuthority = false)] + public void CmdSendInt(int someInt, NetworkConnectionToClient conn = null) => + onSendInt?.Invoke(someInt, conn); + } + + class ThrowBehaviour : NetworkBehaviour + { + public const string ErrorMessage = "Bad things happened"; + + [Command] + public void SendThrow(int _) => throw new Exception(ErrorMessage); + } + + class CommandOverloads : NetworkBehaviour + { + public int firstCalled = 0; + public int secondCalled = 0; + + [Command] + public void TheCommand(int _) => ++firstCalled; + + [Command] + public void TheCommand(string _) => ++secondCalled; + } + + public class CommandTest : RemoteTestBase + { + [Test] + public void CommandIsSentWithAuthority() + { + // spawn with owner + CreateNetworkedAndSpawn(out _, out _, out AuthorityBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int callCount = 0; + hostBehaviour.onSendInt += incomingInt => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.SendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void WarningForCommandSentWithoutAuthority() + { + // spawn without owner + CreateNetworkedAndSpawn(out _, out _, out AuthorityBehaviour hostBehaviour); + + const int someInt = 20; + + int callCount = 0; + hostBehaviour.onSendInt += incomingInt => + { + callCount++; + }; + LogAssert.Expect(LogType.Warning, $"Trying to send command for object without authority. System.Void Mirror.Tests.RemoteAttrributeTest.AuthorityBehaviour::SendInt(System.Int32)"); + hostBehaviour.SendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.Zero); + } + + + [Test] + public void CommandIsSentWithAuthorityWhenIgnoringAuthority() + { + // spawn with owner + CreateNetworkedAndSpawn(out _, out _, out IgnoreAuthorityBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int callCount = 0; + hostBehaviour.onSendInt += incomingInt => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void CommandIsSentWithoutAuthorityWhenIgnoringAuthority() + { + // spawn without owner + CreateNetworkedAndSpawn(out _, out _, out IgnoreAuthorityBehaviour hostBehaviour); + + const int someInt = 20; + + int callCount = 0; + hostBehaviour.onSendInt += incomingInt => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + // test to prevent https://github.com/vis2k/Mirror/issues/2629 + // from happening again in the future + // -> [Command]s can be called on other objects with requiresAuthority=false. + // -> those objects don't have a .connectionToServer + // -> we broke it when using .connectionToServer instead of + // NetworkClient.connection in SendCommandInternal. + [Test] + public void Command_RequiresAuthorityFalse_ForOtherObjectWithoutConnectionToServer() + { + // spawn without owner (= without connectionToClient) + CreateNetworkedAndSpawn(out _, out _, out IgnoreAuthorityBehaviour comp); + + // setup callback + int called = 0; + comp.onSendInt += _ => { ++called; }; + + // call command. don't require authority. + // the object doesn't have a .connectionToServer (like a scene object) + Assert.That(comp.connectionToServer, Is.Null); + comp.CmdSendInt(0); + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void SenderConnectionIsSetWhenCommandIsRecieved() + { + // spawn with owner + CreateNetworkedAndSpawn(out _, out _, out SenderConnectionBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + NetworkConnectionToClient connectionToClient = NetworkServer.connections[0]; + Debug.Assert(connectionToClient != null, $"connectionToClient was null, This means that the test is broken and will give the wrong results"); + + + int callCount = 0; + hostBehaviour.onSendInt += (incomingInt, incomingConn) => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + Assert.That(incomingConn, Is.EqualTo(connectionToClient)); + }; + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void SenderConnectionIsSetWhenCommandIsRecievedWithIgnoreAuthority() + { + // spawn without owner + CreateNetworkedAndSpawn(out _, out _, out SenderConnectionIgnoreAuthorityBehaviour hostBehaviour); + + const int someInt = 20; + NetworkConnectionToClient connectionToClient = NetworkServer.connections[0]; + Debug.Assert(connectionToClient != null, $"connectionToClient was null, This means that the test is broken and will give the wrong results"); + + int callCount = 0; + hostBehaviour.onSendInt += (incomingInt, incomingConn) => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + Assert.That(incomingConn, Is.EqualTo(connectionToClient)); + }; + hostBehaviour.CmdSendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void CommandThatThrowsShouldBeCaught() + { + // spawn with owner + CreateNetworkedAndSpawn(out _, out _, out ThrowBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + NetworkConnectionToClient connectionToClient = NetworkServer.connections[0]; + Debug.Assert(connectionToClient != null, $"connectionToClient was null, This means that the test is broken and will give the wrong results"); + + LogAssert.Expect(LogType.Error, new Regex($".*{ThrowBehaviour.ErrorMessage}.*")); + Assert.DoesNotThrow(() => + { + hostBehaviour.SendThrow(someInt); + ProcessMessages(); + }, "Processing new message should not throw, the exception from SendThrow should be caught"); + } + + // RemoteCalls uses md.FullName which gives us the full command/rpc name + // like "System.Void Mirror.Tests.RemoteAttrributeTest.AuthorityBehaviour::SendInt(System.Int32)" + // which means overloads with same name but different types should work. + [Test] + public void CommandOverloads() + { + // spawn with owner + CreateNetworkedAndSpawn(out _, out _, out CommandOverloads comp, NetworkServer.localConnection); + + // call both overloads once + comp.TheCommand(42); + comp.TheCommand("A"); + ProcessMessages(); + Assert.That(comp.firstCalled, Is.EqualTo(1)); + Assert.That(comp.secondCalled, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/CommandTest.cs.meta b/Assets/Mirror/Tests/Editor/CommandTest.cs.meta new file mode 100644 index 000000000..4d4917ddf --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CommandTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1dd3f8a95eee6f74997bc8abcd43a401 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/CompressionTests.cs b/Assets/Mirror/Tests/Editor/CompressionTests.cs new file mode 100644 index 000000000..b03ec36f0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CompressionTests.cs @@ -0,0 +1,257 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class CompressionTests + { + [Test] + public void LargestAbsoluteComponentIndex() + { + // positive value & xyw smallest + Vector4 value = new Vector4(1, 3, 4, 2); + int index = Compression.LargestAbsoluteComponentIndex(value, out float largest, out Vector3 withoutLargest); + Assert.That(index, Is.EqualTo(2)); + Assert.That(largest, Is.EqualTo(Mathf.Abs(value.z))); + Assert.That(withoutLargest, Is.EqualTo(new Vector3(value.x, value.y, value.w))); + + // negative value should use abs & xzw smallest + value = new Vector4(1, -5, 4, 0); + index = Compression.LargestAbsoluteComponentIndex(value, out largest, out withoutLargest); + Assert.That(index, Is.EqualTo(1)); + Assert.That(largest, Is.EqualTo(Mathf.Abs(value.y))); + Assert.That(withoutLargest, Is.EqualTo(new Vector3(value.x, value.z, value.w))); + + // positive value & yzw smallest + value = new Vector4(5, 2, 3, 4); + index = Compression.LargestAbsoluteComponentIndex(value, out largest, out withoutLargest); + Assert.That(index, Is.EqualTo(0)); + Assert.That(largest, Is.EqualTo(Mathf.Abs(value.x))); + Assert.That(withoutLargest, Is.EqualTo(new Vector3(value.y, value.z, value.w))); + + // test to guarantee it uses 'abs' for first value + // to reproduce https://github.com/vis2k/Mirror/issues/2674 + // IF all values are properly 'abs', THEN first one should be largest + value = new Vector4(-3, 0, 1, 2); + index = Compression.LargestAbsoluteComponentIndex(value, out largest, out withoutLargest); + Assert.That(index, Is.EqualTo(0)); + Assert.That(largest, Is.EqualTo(Mathf.Abs(value.x))); + Assert.That(withoutLargest, Is.EqualTo(new Vector3(value.y, value.z, value.w))); + } + + [Test, Ignore("Enable when needed.")] + public void LargestAbsoluteComponentIndexBenchmark() + { + Vector4 value = new Vector4(1, 2, 3, 4); + for (int i = 0; i < 100000; ++i) + Compression.LargestAbsoluteComponentIndex(value, out float _, out Vector3 _); + } + + [Test] + public void ScaleFloatToUShort() + { + Assert.That(Compression.ScaleFloatToUShort(-1f, -1f, 1f, ushort.MinValue, ushort.MaxValue), Is.EqualTo(0)); + Assert.That(Compression.ScaleFloatToUShort(0f, -1f, 1f, ushort.MinValue, ushort.MaxValue), Is.EqualTo(32767)); + Assert.That(Compression.ScaleFloatToUShort(0.5f, -1f, 1f, ushort.MinValue, ushort.MaxValue), Is.EqualTo(49151)); + Assert.That(Compression.ScaleFloatToUShort(1f, -1f, 1f, ushort.MinValue, ushort.MaxValue), Is.EqualTo(65535)); + } + + [Test] + public void ScaleUShortToFloat() + { + Assert.That(Compression.ScaleUShortToFloat(0, ushort.MinValue, ushort.MaxValue, -1, 1), Is.EqualTo(-1).Within(0.0001f)); + Assert.That(Compression.ScaleUShortToFloat(32767, ushort.MinValue, ushort.MaxValue, -1, 1), Is.EqualTo(-0f).Within(0.0001f)); + Assert.That(Compression.ScaleUShortToFloat(49151, ushort.MinValue, ushort.MaxValue, -1, 1), Is.EqualTo(0.5f).Within(0.0001f)); + Assert.That(Compression.ScaleUShortToFloat(65535, ushort.MinValue, ushort.MaxValue, -1, 1), Is.EqualTo(1).Within(0.0001f)); + } + + [Test] + public void CompressAndDecompressQuaternion() + { + // we need a normalized value + Quaternion value = new Quaternion(1, 3, 4, 2).normalized; + + // compress + uint data = Compression.CompressQuaternion(value); + Assert.That(data, Is.EqualTo(0xA83E2F07)); + + // decompress + Quaternion decompressed = Compression.DecompressQuaternion(data); + Assert.That(decompressed.x, Is.EqualTo(value.x).Within(0.005f)); + Assert.That(decompressed.y, Is.EqualTo(value.y).Within(0.005f)); + Assert.That(decompressed.z, Is.EqualTo(value.z).Within(0.005f)); + Assert.That(decompressed.w, Is.EqualTo(value.w).Within(0.005f)); + } + + // iterate all [0..360] euler angles for x, y, z + // to make sure it all works and we missed nothing. + [Test] + public void CompressAndDecompressQuaternion_Iterate_0_to_360() + { + // stepSize 1: 360 * 360 * 360 = 46 million [takes 96 s] + // stepSize 5: 72 * 72 * 72 = 373 thousand [takes 700 ms] + // stepSize 10: 36 * 36 * 36 = 46 thousand [takes 100 ms] + // + // => 10 is enough. 700ms accumulates in hours of time waited over + // the years.. + const int stepSize = 10; + + for (int x = 0; x <= 360; x += stepSize) + { + for (int y = 0; y <= 360; y += stepSize) + { + for (int z = 0; z <= 360; z += stepSize) + { + // we need a normalized value + Quaternion value = Quaternion.Euler(x, y, z).normalized; + + // compress + uint data = Compression.CompressQuaternion(value); + + // decompress + Quaternion decompressed = Compression.DecompressQuaternion(data); + + // compare them. Quaternion.Angle is easiest to get the angle + // between them. using .eulerAngles would give 0, 90, 360 which is + // hard to compare. + float angle = Quaternion.Angle(value, decompressed); + // 1 degree tolerance + Assert.That(Mathf.Abs(angle), Is.LessThanOrEqualTo(1)); + } + } + } + } + + // someone mentioned issues with 90 degree euler becoming -90 degree + [Test] + public void CompressAndDecompressQuaternion_90DegreeEuler() + { + // we need a normalized value + Quaternion value = Quaternion.Euler(0, 90, 0).normalized; + + // compress + uint data = Compression.CompressQuaternion(value); + + // decompress + Quaternion decompressed = Compression.DecompressQuaternion(data); + + // compare them. Quaternion.Angle is easiest to get the angle + // between them. using .eulerAngles would give 0, 90, 360 which is + // hard to compare. + Debug.Log($"euler={decompressed.eulerAngles}"); + float angle = Quaternion.Angle(value, decompressed); + // 1 degree tolerance + Assert.That(Mathf.Abs(angle), Is.LessThanOrEqualTo(1)); + } + + // test for issue https://github.com/vis2k/Mirror/issues/2674 + [Test] + public void CompressAndDecompressQuaternion_2674() + { + // we need a normalized value + Quaternion value = Quaternion.Euler(338.850037f, 170.609955f, 182.979996f).normalized; + Debug.Log($"original={value.eulerAngles}"); + + // compress + uint data = Compression.CompressQuaternion(value); + + // decompress + Quaternion decompressed = Compression.DecompressQuaternion(data); + + // compare them. Quaternion.Angle is easiest to get the angle + // between them. using .eulerAngles would give 0, 90, 360 which is + // hard to compare. + + // (51.6, 355.5, 348.1) + Debug.Log($"euler={decompressed.eulerAngles}"); + float angle = Quaternion.Angle(value, decompressed); + // 1 degree tolerance + Assert.That(Mathf.Abs(angle), Is.LessThanOrEqualTo(1)); + } + + // client sending invalid data should still produce valid quaternions to + // avoid any possible bugs on server + [Test] + public void DecompressQuaternionInvalidData() + { + // decompress + // 0xFFFFFFFF will decompress to (0.7, 0.7, 0.7, NaN) + Quaternion decompressed = Compression.DecompressQuaternion(0xFFFFFFFF); + Assert.That(decompressed, Is.EqualTo(Quaternion.identity)); + } + + [Test] + public void VarInt() + { + NetworkWriter writer = new NetworkWriter(); + Compression.CompressVarUInt(writer, 0); + Compression.CompressVarUInt(writer, 234); + Compression.CompressVarUInt(writer, 2284); + Compression.CompressVarUInt(writer, 67821); + Compression.CompressVarUInt(writer, 16777210); + Compression.CompressVarUInt(writer, 16777219); + Compression.CompressVarUInt(writer, 4294967295); + Compression.CompressVarUInt(writer, 1099511627775); + Compression.CompressVarUInt(writer, 281474976710655); + Compression.CompressVarUInt(writer, 72057594037927935); + Compression.CompressVarUInt(writer, ulong.MaxValue); + + Compression.CompressVarInt(writer, long.MinValue); + Compression.CompressVarInt(writer, -72057594037927935); + Compression.CompressVarInt(writer, -281474976710655); + Compression.CompressVarInt(writer, -1099511627775); + Compression.CompressVarInt(writer, -4294967295); + Compression.CompressVarInt(writer, -16777219); + Compression.CompressVarInt(writer, -16777210); + Compression.CompressVarInt(writer, -67821); + Compression.CompressVarInt(writer, -2284); + Compression.CompressVarInt(writer, -234); + Compression.CompressVarInt(writer, 0); + Compression.CompressVarInt(writer, 234); + Compression.CompressVarInt(writer, 2284); + Compression.CompressVarInt(writer, 67821); + Compression.CompressVarInt(writer, 16777210); + Compression.CompressVarInt(writer, 16777219); + Compression.CompressVarInt(writer, 4294967295); + Compression.CompressVarInt(writer, 1099511627775); + Compression.CompressVarInt(writer, 281474976710655); + Compression.CompressVarInt(writer, 72057594037927935); + Compression.CompressVarInt(writer, long.MaxValue); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(0)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(234)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(2284)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(67821)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(16777210)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(16777219)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(4294967295)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(1099511627775)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(281474976710655)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(72057594037927935)); + Assert.That(Compression.DecompressVarUInt(reader), Is.EqualTo(ulong.MaxValue)); + + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(long.MinValue)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-72057594037927935)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-281474976710655)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-1099511627775)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-4294967295)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-16777219)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-16777210)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-67821)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-2284)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(-234)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(0)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(234)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(2284)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(67821)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(16777210)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(16777219)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(4294967295)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(1099511627775)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(281474976710655)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(72057594037927935)); + Assert.That(Compression.DecompressVarInt(reader), Is.EqualTo(long.MaxValue)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/CompressionTests.cs.meta b/Assets/Mirror/Tests/Editor/CompressionTests.cs.meta new file mode 100644 index 000000000..b1487c00a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CompressionTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 541cf2bc89fe4395b2a50d921c91424a +timeCreated: 1613190697 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/CustomRWTest.cs b/Assets/Mirror/Tests/Editor/CustomRWTest.cs new file mode 100644 index 000000000..3b015b383 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CustomRWTest.cs @@ -0,0 +1,53 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class MockQuest + { + public int Id; + + public MockQuest(int id) + { + Id = id; + } + + public MockQuest() + { + Id = 0; + } + } + + public static class MockQuestReaderWriter + { + public static void WriteQuest(this NetworkWriter writer, MockQuest quest) + { + writer.WriteInt(quest.Id); + } + public static MockQuest WriteQuest(this NetworkReader reader) + { + return new MockQuest(reader.ReadInt()); + } + } + + [TestFixture] + public class CustomRWTest + { + public struct QuestMessage : NetworkMessage + { + public MockQuest quest; + } + + [Test] + public void TestCustomRW() + { + QuestMessage message = new QuestMessage + { + quest = new MockQuest(100) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + QuestMessage unpacked = MessagePackingTest.UnpackFromByteArray(data); + Assert.That(unpacked.quest.Id, Is.EqualTo(100)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/CustomRWTest.cs.meta b/Assets/Mirror/Tests/Editor/CustomRWTest.cs.meta new file mode 100644 index 000000000..d1adf21d0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/CustomRWTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d74d53ca2c8c4b1195833376f9f6bb6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs b/Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs new file mode 100644 index 000000000..a37e18245 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs @@ -0,0 +1,91 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + public static class MyCustomEnumReadWrite + { + public static void WriteMyCustomEnum(this NetworkWriter networkWriter, EnumReadWriteTests.MyCustomEnum customEnum) + { + // if O write N + if (customEnum == EnumReadWriteTests.MyCustomEnum.O) + { + networkWriter.WriteInt((int)EnumReadWriteTests.MyCustomEnum.N); + } + else + { + networkWriter.WriteInt((int)customEnum); + } + } + public static EnumReadWriteTests.MyCustomEnum ReadMyCustomEnum(this NetworkReader networkReader) + { + return (EnumReadWriteTests.MyCustomEnum)networkReader.ReadInt(); + } + } + public class EnumReadWriteTests + { + public struct ByteMessage : NetworkMessage { public MyByteEnum byteEnum; } + public enum MyByteEnum : byte + { + A, B, C, D + } + + public struct ShortMessage : NetworkMessage { public MyShortEnum shortEnum; } + public enum MyShortEnum : short + { + E, F, G, H + } + + public struct CustomMessage : NetworkMessage { public MyCustomEnum customEnum; } + + public enum MyCustomEnum + { + M, N, O, P + } + + + [Test] + public void ByteIsSentForByteEnum() + { + ByteMessage msg = new ByteMessage() { byteEnum = MyByteEnum.B }; + + NetworkWriter writer = new NetworkWriter(); + writer.Write(msg); + + // should be 1 byte for data + Assert.That(writer.Position, Is.EqualTo(1)); + } + + [Test] + public void ShortIsSentForShortEnum() + { + ShortMessage msg = new ShortMessage() { shortEnum = MyShortEnum.G }; + + NetworkWriter writer = new NetworkWriter(); + writer.Write(msg); + + // should be 2 bytes for data + Assert.That(writer.Position, Is.EqualTo(2)); + } + + [Test] + public void CustomWriterIsUsedForEnum() + { + CustomMessage serverMsg = new CustomMessage() { customEnum = MyCustomEnum.O }; + CustomMessage clientMsg = SerializeAndDeserializeMessage(serverMsg); + + // custom writer should write N if it sees O + Assert.That(clientMsg.customEnum, Is.EqualTo(MyCustomEnum.N)); + } + T SerializeAndDeserializeMessage(T msg) + where T : struct, NetworkMessage + { + NetworkWriter writer = new NetworkWriter(); + + writer.Write(msg); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + return reader.Read(); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs.meta b/Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs.meta new file mode 100644 index 000000000..5832f084c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/EnumReadWriteTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 79e6cd90456eed340a72b1bdb6fe7e49 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs new file mode 100644 index 000000000..3b55264c4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; +namespace Mirror.Tests +{ + [TestFixture] + public class ExponentialMovingAverageTest + { + [Test] + public void TestInitial() + { + ExponentialMovingAverage ema = new ExponentialMovingAverage(10); + + ema.Add(3); + + Assert.That(ema.Value, Is.EqualTo(3)); + Assert.That(ema.Var, Is.EqualTo(0)); + } + + [Test] + public void TestMovingAverage() + { + ExponentialMovingAverage ema = new ExponentialMovingAverage(10); + + ema.Add(5); + ema.Add(6); + + Assert.That(ema.Value, Is.EqualTo(5.1818).Within(0.0001f)); + Assert.That(ema.Var, Is.EqualTo(0.1487).Within(0.0001f)); + } + + [Test] + public void TestVar() + { + ExponentialMovingAverage ema = new ExponentialMovingAverage(10); + + ema.Add(5); + ema.Add(6); + ema.Add(7); + + Assert.That(ema.Var, Is.EqualTo(0.6134).Within(0.0001f)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta new file mode 100644 index 000000000..535f33d77 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ExponentialMovingAverageTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8e3f2ecadd13149f29cd3e83ef6a4bff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ExtensionsTest.cs b/Assets/Mirror/Tests/Editor/ExtensionsTest.cs new file mode 100644 index 000000000..ef1e0d8ab --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ExtensionsTest.cs @@ -0,0 +1,26 @@ +using System.Collections.Generic; +using System.Linq; +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class ExtensionsTest + { + // supposed to return same result on all platforms + [Test] + public void GetStableHashHode() + { + Assert.That("".GetStableHashCode(), Is.EqualTo(23)); + Assert.That("Test".GetStableHashCode(), Is.EqualTo(23844169)); + } + + [Test] + public void CopyToList() + { + List source = new List{1, 2, 3}; + List destination = new List(); + source.CopyTo(destination); + Assert.That(destination.SequenceEqual(source), Is.True); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/ExtensionsTest.cs.meta b/Assets/Mirror/Tests/Editor/ExtensionsTest.cs.meta new file mode 100644 index 000000000..bc31df854 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ExtensionsTest.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 21e1452d6f734618a9364a2c6c116922 +timeCreated: 1621762116 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs b/Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs new file mode 100644 index 000000000..8d1ee0fe7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs @@ -0,0 +1,56 @@ +using System; +using Mirror.Tests.RemoteAttrributeTest; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.GeneratedWriterTests +{ + public class BaseData + { + public bool toggle; + } + public class SomeOtherData : BaseData + { + public int usefulNumber; + } + + public class DataSenderBehaviour : NetworkBehaviour + { + public event Action onData; + + [Command] + public void CmdSendData(SomeOtherData otherData) + { + onData?.Invoke(otherData); + } + } + + public class FieldsInBaseClasses : RemoteTestBase + { + [Test, Ignore("Destroy is needed for the code. Can't be called in Edit mode.")] + public void WriterShouldIncludeFieldsInBaseClass() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out DataSenderBehaviour hostBehaviour, NetworkServer.localConnection); + + const bool toggle = true; + const int usefulNumber = 10; + + int called = 0; + hostBehaviour.onData += data => + { + called++; + Assert.That(data.usefulNumber, Is.EqualTo(usefulNumber)); + Assert.That(data.toggle, Is.EqualTo(toggle)); + }; + hostBehaviour.CmdSendData(new SomeOtherData + { + usefulNumber = usefulNumber, + toggle = toggle + }); + + ProcessMessages(); + Assert.That(called, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs.meta b/Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs.meta new file mode 100644 index 000000000..67a428572 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/FieldsInBaseClasses.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 518288ffe7c7215458a98466131fc7af +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Generated.meta b/Assets/Mirror/Tests/Editor/Generated.meta new file mode 100644 index 000000000..be2f418af --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Generated.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: f1a04c2c41f19ea46b0b1a33c8f2ae89 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs b/Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs new file mode 100644 index 000000000..773567b10 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs @@ -0,0 +1,5946 @@ +// Generated by AttributeTestGenerator.cs +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Generated.Attributes +{ + public class ClassWithNoConstructor + { + public int a; + } + + public class ClassWithConstructor + { + public int a; + + public ClassWithConstructor(int a) + { + this.a = a; + } + } + + public class AttributeBehaviour_NetworkBehaviour : NetworkBehaviour + { + public static readonly float Expected_float = 2020f; + public static readonly double Expected_double = 2.54; + public static readonly bool Expected_bool = true; + public static readonly char Expected_char = 'a'; + public static readonly byte Expected_byte = 224; + public static readonly int Expected_int = 103; + public static readonly long Expected_long = -123456789L; + public static readonly ulong Expected_ulong = 123456789UL; + public static readonly Vector3 Expected_Vector3 = new Vector3(29, 1, 10); + public static readonly ClassWithNoConstructor Expected_ClassWithNoConstructor = new ClassWithNoConstructor { a = 10 }; + public static readonly ClassWithConstructor Expected_ClassWithConstructor = new ClassWithConstructor(29); + + + [Client] + public float Client_float_Function() + { + return Expected_float; + } + + [Client] + public void Client_float_out_Function(out float value) + { + value = Expected_float; + } + + [Client] + public double Client_double_Function() + { + return Expected_double; + } + + [Client] + public void Client_double_out_Function(out double value) + { + value = Expected_double; + } + + [Client] + public bool Client_bool_Function() + { + return Expected_bool; + } + + [Client] + public void Client_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [Client] + public char Client_char_Function() + { + return Expected_char; + } + + [Client] + public void Client_char_out_Function(out char value) + { + value = Expected_char; + } + + [Client] + public byte Client_byte_Function() + { + return Expected_byte; + } + + [Client] + public void Client_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [Client] + public int Client_int_Function() + { + return Expected_int; + } + + [Client] + public void Client_int_out_Function(out int value) + { + value = Expected_int; + } + + [Client] + public long Client_long_Function() + { + return Expected_long; + } + + [Client] + public void Client_long_out_Function(out long value) + { + value = Expected_long; + } + + [Client] + public ulong Client_ulong_Function() + { + return Expected_ulong; + } + + [Client] + public void Client_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [Client] + public Vector3 Client_Vector3_Function() + { + return Expected_Vector3; + } + + [Client] + public void Client_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [Client] + public ClassWithNoConstructor Client_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [Client] + public void Client_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [Client] + public ClassWithConstructor Client_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [Client] + public void Client_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [Server] + public float Server_float_Function() + { + return Expected_float; + } + + [Server] + public void Server_float_out_Function(out float value) + { + value = Expected_float; + } + + [Server] + public double Server_double_Function() + { + return Expected_double; + } + + [Server] + public void Server_double_out_Function(out double value) + { + value = Expected_double; + } + + [Server] + public bool Server_bool_Function() + { + return Expected_bool; + } + + [Server] + public void Server_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [Server] + public char Server_char_Function() + { + return Expected_char; + } + + [Server] + public void Server_char_out_Function(out char value) + { + value = Expected_char; + } + + [Server] + public byte Server_byte_Function() + { + return Expected_byte; + } + + [Server] + public void Server_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [Server] + public int Server_int_Function() + { + return Expected_int; + } + + [Server] + public void Server_int_out_Function(out int value) + { + value = Expected_int; + } + + [Server] + public long Server_long_Function() + { + return Expected_long; + } + + [Server] + public void Server_long_out_Function(out long value) + { + value = Expected_long; + } + + [Server] + public ulong Server_ulong_Function() + { + return Expected_ulong; + } + + [Server] + public void Server_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [Server] + public Vector3 Server_Vector3_Function() + { + return Expected_Vector3; + } + + [Server] + public void Server_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [Server] + public ClassWithNoConstructor Server_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [Server] + public void Server_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [Server] + public ClassWithConstructor Server_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [Server] + public void Server_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [ClientCallback] + public float ClientCallback_float_Function() + { + return Expected_float; + } + + [ClientCallback] + public void ClientCallback_float_out_Function(out float value) + { + value = Expected_float; + } + + [ClientCallback] + public double ClientCallback_double_Function() + { + return Expected_double; + } + + [ClientCallback] + public void ClientCallback_double_out_Function(out double value) + { + value = Expected_double; + } + + [ClientCallback] + public bool ClientCallback_bool_Function() + { + return Expected_bool; + } + + [ClientCallback] + public void ClientCallback_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [ClientCallback] + public char ClientCallback_char_Function() + { + return Expected_char; + } + + [ClientCallback] + public void ClientCallback_char_out_Function(out char value) + { + value = Expected_char; + } + + [ClientCallback] + public byte ClientCallback_byte_Function() + { + return Expected_byte; + } + + [ClientCallback] + public void ClientCallback_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [ClientCallback] + public int ClientCallback_int_Function() + { + return Expected_int; + } + + [ClientCallback] + public void ClientCallback_int_out_Function(out int value) + { + value = Expected_int; + } + + [ClientCallback] + public long ClientCallback_long_Function() + { + return Expected_long; + } + + [ClientCallback] + public void ClientCallback_long_out_Function(out long value) + { + value = Expected_long; + } + + [ClientCallback] + public ulong ClientCallback_ulong_Function() + { + return Expected_ulong; + } + + [ClientCallback] + public void ClientCallback_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [ClientCallback] + public Vector3 ClientCallback_Vector3_Function() + { + return Expected_Vector3; + } + + [ClientCallback] + public void ClientCallback_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [ClientCallback] + public ClassWithNoConstructor ClientCallback_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [ClientCallback] + public void ClientCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [ClientCallback] + public ClassWithConstructor ClientCallback_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [ClientCallback] + public void ClientCallback_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [ServerCallback] + public float ServerCallback_float_Function() + { + return Expected_float; + } + + [ServerCallback] + public void ServerCallback_float_out_Function(out float value) + { + value = Expected_float; + } + + [ServerCallback] + public double ServerCallback_double_Function() + { + return Expected_double; + } + + [ServerCallback] + public void ServerCallback_double_out_Function(out double value) + { + value = Expected_double; + } + + [ServerCallback] + public bool ServerCallback_bool_Function() + { + return Expected_bool; + } + + [ServerCallback] + public void ServerCallback_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [ServerCallback] + public char ServerCallback_char_Function() + { + return Expected_char; + } + + [ServerCallback] + public void ServerCallback_char_out_Function(out char value) + { + value = Expected_char; + } + + [ServerCallback] + public byte ServerCallback_byte_Function() + { + return Expected_byte; + } + + [ServerCallback] + public void ServerCallback_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [ServerCallback] + public int ServerCallback_int_Function() + { + return Expected_int; + } + + [ServerCallback] + public void ServerCallback_int_out_Function(out int value) + { + value = Expected_int; + } + + [ServerCallback] + public long ServerCallback_long_Function() + { + return Expected_long; + } + + [ServerCallback] + public void ServerCallback_long_out_Function(out long value) + { + value = Expected_long; + } + + [ServerCallback] + public ulong ServerCallback_ulong_Function() + { + return Expected_ulong; + } + + [ServerCallback] + public void ServerCallback_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [ServerCallback] + public Vector3 ServerCallback_Vector3_Function() + { + return Expected_Vector3; + } + + [ServerCallback] + public void ServerCallback_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [ServerCallback] + public ClassWithNoConstructor ServerCallback_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [ServerCallback] + public void ServerCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [ServerCallback] + public ClassWithConstructor ServerCallback_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [ServerCallback] + public void ServerCallback_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + } + + + public class AttributeTest_NetworkBehaviour + { + AttributeBehaviour_NetworkBehaviour behaviour; + GameObject go; + + [OneTimeSetUp] + public void SetUp() + { + go = new GameObject(); + behaviour = go.AddComponent(); + } + + [OneTimeTearDown] + public void TearDown() + { + UnityEngine.Object.DestroyImmediate(go); + NetworkClient.connectState = ConnectState.None; + NetworkServer.active = false; + } + + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_float_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Single Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_float_Function()' called when client was not active"); + } + float actual = behaviour.Client_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_float_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_float_out_Function(System.Single&)' called when client was not active"); + } + behaviour.Client_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_double_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Double Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_double_Function()' called when client was not active"); + } + double actual = behaviour.Client_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_double_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_double_out_Function(System.Double&)' called when client was not active"); + } + behaviour.Client_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_bool_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Boolean Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_bool_Function()' called when client was not active"); + } + bool actual = behaviour.Client_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_bool_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_bool_out_Function(System.Boolean&)' called when client was not active"); + } + behaviour.Client_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_char_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Char Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_char_Function()' called when client was not active"); + } + char actual = behaviour.Client_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_char_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_char_out_Function(System.Char&)' called when client was not active"); + } + behaviour.Client_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_byte_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Byte Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_byte_Function()' called when client was not active"); + } + byte actual = behaviour.Client_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_byte_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_byte_out_Function(System.Byte&)' called when client was not active"); + } + behaviour.Client_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_int_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Int32 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_int_Function()' called when client was not active"); + } + int actual = behaviour.Client_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_int_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_int_out_Function(System.Int32&)' called when client was not active"); + } + behaviour.Client_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_long_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Int64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_long_Function()' called when client was not active"); + } + long actual = behaviour.Client_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_long_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_long_out_Function(System.Int64&)' called when client was not active"); + } + behaviour.Client_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ulong_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.UInt64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_ulong_Function()' called when client was not active"); + } + ulong actual = behaviour.Client_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ulong_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_ulong_out_Function(System.UInt64&)' called when client was not active"); + } + behaviour.Client_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_Vector3_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'UnityEngine.Vector3 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_Vector3_Function()' called when client was not active"); + } + Vector3 actual = behaviour.Client_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_Vector3_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_Vector3_out_Function(UnityEngine.Vector3&)' called when client was not active"); + } + behaviour.Client_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'Mirror.Tests.Generated.Attributes.ClassWithNoConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_ClassWithNoConstructor_Function()' called when client was not active"); + } + ClassWithNoConstructor actual = behaviour.Client_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_ClassWithNoConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithNoConstructor&)' called when client was not active"); + } + behaviour.Client_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'Mirror.Tests.Generated.Attributes.ClassWithConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_ClassWithConstructor_Function()' called when client was not active"); + } + ClassWithConstructor actual = behaviour.Client_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Client_ClassWithConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithConstructor&)' called when client was not active"); + } + behaviour.Client_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_float_returnsValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Single Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_float_Function()' called when server was not active"); + } + float actual = behaviour.Server_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_float_setsOutValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_float_out_Function(System.Single&)' called when server was not active"); + } + behaviour.Server_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_double_returnsValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Double Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_double_Function()' called when server was not active"); + } + double actual = behaviour.Server_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_double_setsOutValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_double_out_Function(System.Double&)' called when server was not active"); + } + behaviour.Server_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_bool_returnsValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Boolean Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_bool_Function()' called when server was not active"); + } + bool actual = behaviour.Server_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_bool_setsOutValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_bool_out_Function(System.Boolean&)' called when server was not active"); + } + behaviour.Server_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_char_returnsValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Char Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_char_Function()' called when server was not active"); + } + char actual = behaviour.Server_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_char_setsOutValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_char_out_Function(System.Char&)' called when server was not active"); + } + behaviour.Server_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_byte_returnsValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Byte Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_byte_Function()' called when server was not active"); + } + byte actual = behaviour.Server_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_byte_setsOutValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_byte_out_Function(System.Byte&)' called when server was not active"); + } + behaviour.Server_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_int_returnsValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Int32 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_int_Function()' called when server was not active"); + } + int actual = behaviour.Server_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_int_setsOutValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_int_out_Function(System.Int32&)' called when server was not active"); + } + behaviour.Server_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_long_returnsValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Int64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_long_Function()' called when server was not active"); + } + long actual = behaviour.Server_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_long_setsOutValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_long_out_Function(System.Int64&)' called when server was not active"); + } + behaviour.Server_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ulong_returnsValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.UInt64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_ulong_Function()' called when server was not active"); + } + ulong actual = behaviour.Server_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ulong_setsOutValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_ulong_out_Function(System.UInt64&)' called when server was not active"); + } + behaviour.Server_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_Vector3_returnsValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'UnityEngine.Vector3 Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_Vector3_Function()' called when server was not active"); + } + Vector3 actual = behaviour.Server_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_Vector3_setsOutValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_Vector3_out_Function(UnityEngine.Vector3&)' called when server was not active"); + } + behaviour.Server_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'Mirror.Tests.Generated.Attributes.ClassWithNoConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_ClassWithNoConstructor_Function()' called when server was not active"); + } + ClassWithNoConstructor actual = behaviour.Server_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_ClassWithNoConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithNoConstructor&)' called when server was not active"); + } + behaviour.Server_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'Mirror.Tests.Generated.Attributes.ClassWithConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_ClassWithConstructor_Function()' called when server was not active"); + } + ClassWithConstructor actual = behaviour.Server_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_NetworkBehaviour::Server_ClassWithConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithConstructor&)' called when server was not active"); + } + behaviour.Server_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_float_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + float actual = behaviour.ClientCallback_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_float_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + behaviour.ClientCallback_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_double_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + double actual = behaviour.ClientCallback_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_double_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + behaviour.ClientCallback_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_bool_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + bool actual = behaviour.ClientCallback_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_bool_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + behaviour.ClientCallback_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_char_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + char actual = behaviour.ClientCallback_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_char_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + behaviour.ClientCallback_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_byte_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + byte actual = behaviour.ClientCallback_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_byte_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + behaviour.ClientCallback_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_int_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + int actual = behaviour.ClientCallback_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_int_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + behaviour.ClientCallback_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_long_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + long actual = behaviour.ClientCallback_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_long_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + behaviour.ClientCallback_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ulong_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + ulong actual = behaviour.ClientCallback_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ulong_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + behaviour.ClientCallback_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_Vector3_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + Vector3 actual = behaviour.ClientCallback_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_Vector3_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + behaviour.ClientCallback_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + ClassWithNoConstructor actual = behaviour.ClientCallback_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + behaviour.ClientCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + ClassWithConstructor actual = behaviour.ClientCallback_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + behaviour.ClientCallback_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_float_returnsValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + float actual = behaviour.ServerCallback_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_float_setsOutValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_float : default; + + behaviour.ServerCallback_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_double_returnsValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + double actual = behaviour.ServerCallback_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_double_setsOutValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_double : default; + + behaviour.ServerCallback_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_bool_returnsValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + bool actual = behaviour.ServerCallback_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_bool_setsOutValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_bool : default; + + behaviour.ServerCallback_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_char_returnsValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + char actual = behaviour.ServerCallback_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_char_setsOutValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_char : default; + + behaviour.ServerCallback_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_byte_returnsValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + byte actual = behaviour.ServerCallback_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_byte_setsOutValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_byte : default; + + behaviour.ServerCallback_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_int_returnsValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + int actual = behaviour.ServerCallback_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_int_setsOutValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_int : default; + + behaviour.ServerCallback_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_long_returnsValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + long actual = behaviour.ServerCallback_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_long_setsOutValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_long : default; + + behaviour.ServerCallback_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ulong_returnsValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + ulong actual = behaviour.ServerCallback_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ulong_setsOutValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ulong : default; + + behaviour.ServerCallback_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_Vector3_returnsValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + Vector3 actual = behaviour.ServerCallback_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_Vector3_setsOutValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_Vector3 : default; + + behaviour.ServerCallback_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + ClassWithNoConstructor actual = behaviour.ServerCallback_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithNoConstructor : default; + + behaviour.ServerCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + ClassWithConstructor actual = behaviour.ServerCallback_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_NetworkBehaviour.Expected_ClassWithConstructor : default; + + behaviour.ServerCallback_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + } + + public class AttributeBehaviour_MonoBehaviour : MonoBehaviour + { + public static readonly float Expected_float = 2020f; + public static readonly double Expected_double = 2.54; + public static readonly bool Expected_bool = true; + public static readonly char Expected_char = 'a'; + public static readonly byte Expected_byte = 224; + public static readonly int Expected_int = 103; + public static readonly long Expected_long = -123456789L; + public static readonly ulong Expected_ulong = 123456789UL; + public static readonly Vector3 Expected_Vector3 = new Vector3(29, 1, 10); + public static readonly ClassWithNoConstructor Expected_ClassWithNoConstructor = new ClassWithNoConstructor { a = 10 }; + public static readonly ClassWithConstructor Expected_ClassWithConstructor = new ClassWithConstructor(29); + + + [Client] + public float Client_float_Function() + { + return Expected_float; + } + + [Client] + public void Client_float_out_Function(out float value) + { + value = Expected_float; + } + + [Client] + public double Client_double_Function() + { + return Expected_double; + } + + [Client] + public void Client_double_out_Function(out double value) + { + value = Expected_double; + } + + [Client] + public bool Client_bool_Function() + { + return Expected_bool; + } + + [Client] + public void Client_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [Client] + public char Client_char_Function() + { + return Expected_char; + } + + [Client] + public void Client_char_out_Function(out char value) + { + value = Expected_char; + } + + [Client] + public byte Client_byte_Function() + { + return Expected_byte; + } + + [Client] + public void Client_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [Client] + public int Client_int_Function() + { + return Expected_int; + } + + [Client] + public void Client_int_out_Function(out int value) + { + value = Expected_int; + } + + [Client] + public long Client_long_Function() + { + return Expected_long; + } + + [Client] + public void Client_long_out_Function(out long value) + { + value = Expected_long; + } + + [Client] + public ulong Client_ulong_Function() + { + return Expected_ulong; + } + + [Client] + public void Client_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [Client] + public Vector3 Client_Vector3_Function() + { + return Expected_Vector3; + } + + [Client] + public void Client_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [Client] + public ClassWithNoConstructor Client_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [Client] + public void Client_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [Client] + public ClassWithConstructor Client_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [Client] + public void Client_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [Server] + public float Server_float_Function() + { + return Expected_float; + } + + [Server] + public void Server_float_out_Function(out float value) + { + value = Expected_float; + } + + [Server] + public double Server_double_Function() + { + return Expected_double; + } + + [Server] + public void Server_double_out_Function(out double value) + { + value = Expected_double; + } + + [Server] + public bool Server_bool_Function() + { + return Expected_bool; + } + + [Server] + public void Server_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [Server] + public char Server_char_Function() + { + return Expected_char; + } + + [Server] + public void Server_char_out_Function(out char value) + { + value = Expected_char; + } + + [Server] + public byte Server_byte_Function() + { + return Expected_byte; + } + + [Server] + public void Server_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [Server] + public int Server_int_Function() + { + return Expected_int; + } + + [Server] + public void Server_int_out_Function(out int value) + { + value = Expected_int; + } + + [Server] + public long Server_long_Function() + { + return Expected_long; + } + + [Server] + public void Server_long_out_Function(out long value) + { + value = Expected_long; + } + + [Server] + public ulong Server_ulong_Function() + { + return Expected_ulong; + } + + [Server] + public void Server_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [Server] + public Vector3 Server_Vector3_Function() + { + return Expected_Vector3; + } + + [Server] + public void Server_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [Server] + public ClassWithNoConstructor Server_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [Server] + public void Server_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [Server] + public ClassWithConstructor Server_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [Server] + public void Server_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [ClientCallback] + public float ClientCallback_float_Function() + { + return Expected_float; + } + + [ClientCallback] + public void ClientCallback_float_out_Function(out float value) + { + value = Expected_float; + } + + [ClientCallback] + public double ClientCallback_double_Function() + { + return Expected_double; + } + + [ClientCallback] + public void ClientCallback_double_out_Function(out double value) + { + value = Expected_double; + } + + [ClientCallback] + public bool ClientCallback_bool_Function() + { + return Expected_bool; + } + + [ClientCallback] + public void ClientCallback_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [ClientCallback] + public char ClientCallback_char_Function() + { + return Expected_char; + } + + [ClientCallback] + public void ClientCallback_char_out_Function(out char value) + { + value = Expected_char; + } + + [ClientCallback] + public byte ClientCallback_byte_Function() + { + return Expected_byte; + } + + [ClientCallback] + public void ClientCallback_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [ClientCallback] + public int ClientCallback_int_Function() + { + return Expected_int; + } + + [ClientCallback] + public void ClientCallback_int_out_Function(out int value) + { + value = Expected_int; + } + + [ClientCallback] + public long ClientCallback_long_Function() + { + return Expected_long; + } + + [ClientCallback] + public void ClientCallback_long_out_Function(out long value) + { + value = Expected_long; + } + + [ClientCallback] + public ulong ClientCallback_ulong_Function() + { + return Expected_ulong; + } + + [ClientCallback] + public void ClientCallback_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [ClientCallback] + public Vector3 ClientCallback_Vector3_Function() + { + return Expected_Vector3; + } + + [ClientCallback] + public void ClientCallback_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [ClientCallback] + public ClassWithNoConstructor ClientCallback_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [ClientCallback] + public void ClientCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [ClientCallback] + public ClassWithConstructor ClientCallback_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [ClientCallback] + public void ClientCallback_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [ServerCallback] + public float ServerCallback_float_Function() + { + return Expected_float; + } + + [ServerCallback] + public void ServerCallback_float_out_Function(out float value) + { + value = Expected_float; + } + + [ServerCallback] + public double ServerCallback_double_Function() + { + return Expected_double; + } + + [ServerCallback] + public void ServerCallback_double_out_Function(out double value) + { + value = Expected_double; + } + + [ServerCallback] + public bool ServerCallback_bool_Function() + { + return Expected_bool; + } + + [ServerCallback] + public void ServerCallback_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [ServerCallback] + public char ServerCallback_char_Function() + { + return Expected_char; + } + + [ServerCallback] + public void ServerCallback_char_out_Function(out char value) + { + value = Expected_char; + } + + [ServerCallback] + public byte ServerCallback_byte_Function() + { + return Expected_byte; + } + + [ServerCallback] + public void ServerCallback_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [ServerCallback] + public int ServerCallback_int_Function() + { + return Expected_int; + } + + [ServerCallback] + public void ServerCallback_int_out_Function(out int value) + { + value = Expected_int; + } + + [ServerCallback] + public long ServerCallback_long_Function() + { + return Expected_long; + } + + [ServerCallback] + public void ServerCallback_long_out_Function(out long value) + { + value = Expected_long; + } + + [ServerCallback] + public ulong ServerCallback_ulong_Function() + { + return Expected_ulong; + } + + [ServerCallback] + public void ServerCallback_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [ServerCallback] + public Vector3 ServerCallback_Vector3_Function() + { + return Expected_Vector3; + } + + [ServerCallback] + public void ServerCallback_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [ServerCallback] + public ClassWithNoConstructor ServerCallback_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [ServerCallback] + public void ServerCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [ServerCallback] + public ClassWithConstructor ServerCallback_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [ServerCallback] + public void ServerCallback_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + } + + + public class AttributeTest_MonoBehaviour + { + AttributeBehaviour_MonoBehaviour behaviour; + GameObject go; + + [OneTimeSetUp] + public void SetUp() + { + go = new GameObject(); + behaviour = go.AddComponent(); + } + + [OneTimeTearDown] + public void TearDown() + { + UnityEngine.Object.DestroyImmediate(go); + NetworkClient.connectState = ConnectState.None; + NetworkServer.active = false; + } + + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_float_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Single Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_float_Function()' called when client was not active"); + } + float actual = behaviour.Client_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_float_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_float_out_Function(System.Single&)' called when client was not active"); + } + behaviour.Client_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_double_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Double Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_double_Function()' called when client was not active"); + } + double actual = behaviour.Client_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_double_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_double_out_Function(System.Double&)' called when client was not active"); + } + behaviour.Client_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_bool_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Boolean Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_bool_Function()' called when client was not active"); + } + bool actual = behaviour.Client_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_bool_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_bool_out_Function(System.Boolean&)' called when client was not active"); + } + behaviour.Client_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_char_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Char Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_char_Function()' called when client was not active"); + } + char actual = behaviour.Client_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_char_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_char_out_Function(System.Char&)' called when client was not active"); + } + behaviour.Client_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_byte_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Byte Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_byte_Function()' called when client was not active"); + } + byte actual = behaviour.Client_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_byte_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_byte_out_Function(System.Byte&)' called when client was not active"); + } + behaviour.Client_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_int_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Int32 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_int_Function()' called when client was not active"); + } + int actual = behaviour.Client_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_int_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_int_out_Function(System.Int32&)' called when client was not active"); + } + behaviour.Client_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_long_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Int64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_long_Function()' called when client was not active"); + } + long actual = behaviour.Client_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_long_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_long_out_Function(System.Int64&)' called when client was not active"); + } + behaviour.Client_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ulong_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.UInt64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_ulong_Function()' called when client was not active"); + } + ulong actual = behaviour.Client_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ulong_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_ulong_out_Function(System.UInt64&)' called when client was not active"); + } + behaviour.Client_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_Vector3_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'UnityEngine.Vector3 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_Vector3_Function()' called when client was not active"); + } + Vector3 actual = behaviour.Client_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_Vector3_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_Vector3_out_Function(UnityEngine.Vector3&)' called when client was not active"); + } + behaviour.Client_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'Mirror.Tests.Generated.Attributes.ClassWithNoConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_ClassWithNoConstructor_Function()' called when client was not active"); + } + ClassWithNoConstructor actual = behaviour.Client_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_ClassWithNoConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithNoConstructor&)' called when client was not active"); + } + behaviour.Client_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'Mirror.Tests.Generated.Attributes.ClassWithConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_ClassWithConstructor_Function()' called when client was not active"); + } + ClassWithConstructor actual = behaviour.Client_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Client_ClassWithConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithConstructor&)' called when client was not active"); + } + behaviour.Client_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_float_returnsValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Single Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_float_Function()' called when server was not active"); + } + float actual = behaviour.Server_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_float_setsOutValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_float_out_Function(System.Single&)' called when server was not active"); + } + behaviour.Server_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_double_returnsValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Double Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_double_Function()' called when server was not active"); + } + double actual = behaviour.Server_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_double_setsOutValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_double_out_Function(System.Double&)' called when server was not active"); + } + behaviour.Server_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_bool_returnsValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Boolean Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_bool_Function()' called when server was not active"); + } + bool actual = behaviour.Server_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_bool_setsOutValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_bool_out_Function(System.Boolean&)' called when server was not active"); + } + behaviour.Server_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_char_returnsValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Char Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_char_Function()' called when server was not active"); + } + char actual = behaviour.Server_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_char_setsOutValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_char_out_Function(System.Char&)' called when server was not active"); + } + behaviour.Server_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_byte_returnsValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Byte Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_byte_Function()' called when server was not active"); + } + byte actual = behaviour.Server_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_byte_setsOutValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_byte_out_Function(System.Byte&)' called when server was not active"); + } + behaviour.Server_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_int_returnsValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Int32 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_int_Function()' called when server was not active"); + } + int actual = behaviour.Server_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_int_setsOutValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_int_out_Function(System.Int32&)' called when server was not active"); + } + behaviour.Server_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_long_returnsValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Int64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_long_Function()' called when server was not active"); + } + long actual = behaviour.Server_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_long_setsOutValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_long_out_Function(System.Int64&)' called when server was not active"); + } + behaviour.Server_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ulong_returnsValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.UInt64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_ulong_Function()' called when server was not active"); + } + ulong actual = behaviour.Server_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ulong_setsOutValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_ulong_out_Function(System.UInt64&)' called when server was not active"); + } + behaviour.Server_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_Vector3_returnsValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'UnityEngine.Vector3 Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_Vector3_Function()' called when server was not active"); + } + Vector3 actual = behaviour.Server_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_Vector3_setsOutValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_Vector3_out_Function(UnityEngine.Vector3&)' called when server was not active"); + } + behaviour.Server_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'Mirror.Tests.Generated.Attributes.ClassWithNoConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_ClassWithNoConstructor_Function()' called when server was not active"); + } + ClassWithNoConstructor actual = behaviour.Server_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_ClassWithNoConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithNoConstructor&)' called when server was not active"); + } + behaviour.Server_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'Mirror.Tests.Generated.Attributes.ClassWithConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_ClassWithConstructor_Function()' called when server was not active"); + } + ClassWithConstructor actual = behaviour.Server_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_MonoBehaviour::Server_ClassWithConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithConstructor&)' called when server was not active"); + } + behaviour.Server_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_float_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + float actual = behaviour.ClientCallback_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_float_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + behaviour.ClientCallback_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_double_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + double actual = behaviour.ClientCallback_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_double_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + behaviour.ClientCallback_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_bool_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + bool actual = behaviour.ClientCallback_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_bool_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + behaviour.ClientCallback_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_char_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + char actual = behaviour.ClientCallback_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_char_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + behaviour.ClientCallback_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_byte_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + byte actual = behaviour.ClientCallback_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_byte_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + behaviour.ClientCallback_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_int_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + int actual = behaviour.ClientCallback_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_int_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + behaviour.ClientCallback_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_long_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + long actual = behaviour.ClientCallback_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_long_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + behaviour.ClientCallback_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ulong_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + ulong actual = behaviour.ClientCallback_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ulong_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + behaviour.ClientCallback_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_Vector3_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + Vector3 actual = behaviour.ClientCallback_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_Vector3_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + behaviour.ClientCallback_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + ClassWithNoConstructor actual = behaviour.ClientCallback_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + behaviour.ClientCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + ClassWithConstructor actual = behaviour.ClientCallback_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + behaviour.ClientCallback_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_float_returnsValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + float actual = behaviour.ServerCallback_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_float_setsOutValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_MonoBehaviour.Expected_float : default; + + behaviour.ServerCallback_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_double_returnsValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + double actual = behaviour.ServerCallback_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_double_setsOutValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_MonoBehaviour.Expected_double : default; + + behaviour.ServerCallback_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_bool_returnsValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + bool actual = behaviour.ServerCallback_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_bool_setsOutValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_MonoBehaviour.Expected_bool : default; + + behaviour.ServerCallback_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_char_returnsValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + char actual = behaviour.ServerCallback_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_char_setsOutValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_MonoBehaviour.Expected_char : default; + + behaviour.ServerCallback_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_byte_returnsValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + byte actual = behaviour.ServerCallback_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_byte_setsOutValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_MonoBehaviour.Expected_byte : default; + + behaviour.ServerCallback_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_int_returnsValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + int actual = behaviour.ServerCallback_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_int_setsOutValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_MonoBehaviour.Expected_int : default; + + behaviour.ServerCallback_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_long_returnsValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + long actual = behaviour.ServerCallback_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_long_setsOutValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_MonoBehaviour.Expected_long : default; + + behaviour.ServerCallback_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ulong_returnsValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + ulong actual = behaviour.ServerCallback_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ulong_setsOutValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ulong : default; + + behaviour.ServerCallback_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_Vector3_returnsValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + Vector3 actual = behaviour.ServerCallback_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_Vector3_setsOutValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_MonoBehaviour.Expected_Vector3 : default; + + behaviour.ServerCallback_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + ClassWithNoConstructor actual = behaviour.ServerCallback_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithNoConstructor : default; + + behaviour.ServerCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + ClassWithConstructor actual = behaviour.ServerCallback_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_MonoBehaviour.Expected_ClassWithConstructor : default; + + behaviour.ServerCallback_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + } + + public class AttributeBehaviour_ClassWithNoConstructor : ClassWithNoConstructor + { + public static readonly float Expected_float = 2020f; + public static readonly double Expected_double = 2.54; + public static readonly bool Expected_bool = true; + public static readonly char Expected_char = 'a'; + public static readonly byte Expected_byte = 224; + public static readonly int Expected_int = 103; + public static readonly long Expected_long = -123456789L; + public static readonly ulong Expected_ulong = 123456789UL; + public static readonly Vector3 Expected_Vector3 = new Vector3(29, 1, 10); + public static readonly ClassWithNoConstructor Expected_ClassWithNoConstructor = new ClassWithNoConstructor { a = 10 }; + public static readonly ClassWithConstructor Expected_ClassWithConstructor = new ClassWithConstructor(29); + + + [Client] + public float Client_float_Function() + { + return Expected_float; + } + + [Client] + public void Client_float_out_Function(out float value) + { + value = Expected_float; + } + + [Client] + public double Client_double_Function() + { + return Expected_double; + } + + [Client] + public void Client_double_out_Function(out double value) + { + value = Expected_double; + } + + [Client] + public bool Client_bool_Function() + { + return Expected_bool; + } + + [Client] + public void Client_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [Client] + public char Client_char_Function() + { + return Expected_char; + } + + [Client] + public void Client_char_out_Function(out char value) + { + value = Expected_char; + } + + [Client] + public byte Client_byte_Function() + { + return Expected_byte; + } + + [Client] + public void Client_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [Client] + public int Client_int_Function() + { + return Expected_int; + } + + [Client] + public void Client_int_out_Function(out int value) + { + value = Expected_int; + } + + [Client] + public long Client_long_Function() + { + return Expected_long; + } + + [Client] + public void Client_long_out_Function(out long value) + { + value = Expected_long; + } + + [Client] + public ulong Client_ulong_Function() + { + return Expected_ulong; + } + + [Client] + public void Client_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [Client] + public Vector3 Client_Vector3_Function() + { + return Expected_Vector3; + } + + [Client] + public void Client_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [Client] + public ClassWithNoConstructor Client_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [Client] + public void Client_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [Client] + public ClassWithConstructor Client_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [Client] + public void Client_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [Server] + public float Server_float_Function() + { + return Expected_float; + } + + [Server] + public void Server_float_out_Function(out float value) + { + value = Expected_float; + } + + [Server] + public double Server_double_Function() + { + return Expected_double; + } + + [Server] + public void Server_double_out_Function(out double value) + { + value = Expected_double; + } + + [Server] + public bool Server_bool_Function() + { + return Expected_bool; + } + + [Server] + public void Server_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [Server] + public char Server_char_Function() + { + return Expected_char; + } + + [Server] + public void Server_char_out_Function(out char value) + { + value = Expected_char; + } + + [Server] + public byte Server_byte_Function() + { + return Expected_byte; + } + + [Server] + public void Server_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [Server] + public int Server_int_Function() + { + return Expected_int; + } + + [Server] + public void Server_int_out_Function(out int value) + { + value = Expected_int; + } + + [Server] + public long Server_long_Function() + { + return Expected_long; + } + + [Server] + public void Server_long_out_Function(out long value) + { + value = Expected_long; + } + + [Server] + public ulong Server_ulong_Function() + { + return Expected_ulong; + } + + [Server] + public void Server_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [Server] + public Vector3 Server_Vector3_Function() + { + return Expected_Vector3; + } + + [Server] + public void Server_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [Server] + public ClassWithNoConstructor Server_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [Server] + public void Server_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [Server] + public ClassWithConstructor Server_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [Server] + public void Server_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [ClientCallback] + public float ClientCallback_float_Function() + { + return Expected_float; + } + + [ClientCallback] + public void ClientCallback_float_out_Function(out float value) + { + value = Expected_float; + } + + [ClientCallback] + public double ClientCallback_double_Function() + { + return Expected_double; + } + + [ClientCallback] + public void ClientCallback_double_out_Function(out double value) + { + value = Expected_double; + } + + [ClientCallback] + public bool ClientCallback_bool_Function() + { + return Expected_bool; + } + + [ClientCallback] + public void ClientCallback_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [ClientCallback] + public char ClientCallback_char_Function() + { + return Expected_char; + } + + [ClientCallback] + public void ClientCallback_char_out_Function(out char value) + { + value = Expected_char; + } + + [ClientCallback] + public byte ClientCallback_byte_Function() + { + return Expected_byte; + } + + [ClientCallback] + public void ClientCallback_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [ClientCallback] + public int ClientCallback_int_Function() + { + return Expected_int; + } + + [ClientCallback] + public void ClientCallback_int_out_Function(out int value) + { + value = Expected_int; + } + + [ClientCallback] + public long ClientCallback_long_Function() + { + return Expected_long; + } + + [ClientCallback] + public void ClientCallback_long_out_Function(out long value) + { + value = Expected_long; + } + + [ClientCallback] + public ulong ClientCallback_ulong_Function() + { + return Expected_ulong; + } + + [ClientCallback] + public void ClientCallback_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [ClientCallback] + public Vector3 ClientCallback_Vector3_Function() + { + return Expected_Vector3; + } + + [ClientCallback] + public void ClientCallback_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [ClientCallback] + public ClassWithNoConstructor ClientCallback_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [ClientCallback] + public void ClientCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [ClientCallback] + public ClassWithConstructor ClientCallback_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [ClientCallback] + public void ClientCallback_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + + [ServerCallback] + public float ServerCallback_float_Function() + { + return Expected_float; + } + + [ServerCallback] + public void ServerCallback_float_out_Function(out float value) + { + value = Expected_float; + } + + [ServerCallback] + public double ServerCallback_double_Function() + { + return Expected_double; + } + + [ServerCallback] + public void ServerCallback_double_out_Function(out double value) + { + value = Expected_double; + } + + [ServerCallback] + public bool ServerCallback_bool_Function() + { + return Expected_bool; + } + + [ServerCallback] + public void ServerCallback_bool_out_Function(out bool value) + { + value = Expected_bool; + } + + [ServerCallback] + public char ServerCallback_char_Function() + { + return Expected_char; + } + + [ServerCallback] + public void ServerCallback_char_out_Function(out char value) + { + value = Expected_char; + } + + [ServerCallback] + public byte ServerCallback_byte_Function() + { + return Expected_byte; + } + + [ServerCallback] + public void ServerCallback_byte_out_Function(out byte value) + { + value = Expected_byte; + } + + [ServerCallback] + public int ServerCallback_int_Function() + { + return Expected_int; + } + + [ServerCallback] + public void ServerCallback_int_out_Function(out int value) + { + value = Expected_int; + } + + [ServerCallback] + public long ServerCallback_long_Function() + { + return Expected_long; + } + + [ServerCallback] + public void ServerCallback_long_out_Function(out long value) + { + value = Expected_long; + } + + [ServerCallback] + public ulong ServerCallback_ulong_Function() + { + return Expected_ulong; + } + + [ServerCallback] + public void ServerCallback_ulong_out_Function(out ulong value) + { + value = Expected_ulong; + } + + [ServerCallback] + public Vector3 ServerCallback_Vector3_Function() + { + return Expected_Vector3; + } + + [ServerCallback] + public void ServerCallback_Vector3_out_Function(out Vector3 value) + { + value = Expected_Vector3; + } + + [ServerCallback] + public ClassWithNoConstructor ServerCallback_ClassWithNoConstructor_Function() + { + return Expected_ClassWithNoConstructor; + } + + [ServerCallback] + public void ServerCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor value) + { + value = Expected_ClassWithNoConstructor; + } + + [ServerCallback] + public ClassWithConstructor ServerCallback_ClassWithConstructor_Function() + { + return Expected_ClassWithConstructor; + } + + [ServerCallback] + public void ServerCallback_ClassWithConstructor_out_Function(out ClassWithConstructor value) + { + value = Expected_ClassWithConstructor; + } + } + + + public class AttributeTest_ClassWithNoConstructor + { + AttributeBehaviour_ClassWithNoConstructor behaviour; + GameObject go; + + [OneTimeSetUp] + public void SetUp() + { + behaviour = new AttributeBehaviour_ClassWithNoConstructor(); + } + + [OneTimeTearDown] + public void TearDown() + { + + NetworkClient.connectState = ConnectState.None; + NetworkServer.active = false; + } + + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_float_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Single Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_float_Function()' called when client was not active"); + } + float actual = behaviour.Client_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_float_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_float_out_Function(System.Single&)' called when client was not active"); + } + behaviour.Client_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_double_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Double Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_double_Function()' called when client was not active"); + } + double actual = behaviour.Client_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_double_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_double_out_Function(System.Double&)' called when client was not active"); + } + behaviour.Client_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_bool_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Boolean Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_bool_Function()' called when client was not active"); + } + bool actual = behaviour.Client_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_bool_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_bool_out_Function(System.Boolean&)' called when client was not active"); + } + behaviour.Client_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_char_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Char Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_char_Function()' called when client was not active"); + } + char actual = behaviour.Client_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_char_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_char_out_Function(System.Char&)' called when client was not active"); + } + behaviour.Client_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_byte_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Byte Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_byte_Function()' called when client was not active"); + } + byte actual = behaviour.Client_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_byte_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_byte_out_Function(System.Byte&)' called when client was not active"); + } + behaviour.Client_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_int_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Int32 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_int_Function()' called when client was not active"); + } + int actual = behaviour.Client_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_int_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_int_out_Function(System.Int32&)' called when client was not active"); + } + behaviour.Client_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_long_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Int64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_long_Function()' called when client was not active"); + } + long actual = behaviour.Client_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_long_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_long_out_Function(System.Int64&)' called when client was not active"); + } + behaviour.Client_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ulong_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.UInt64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_ulong_Function()' called when client was not active"); + } + ulong actual = behaviour.Client_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ulong_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_ulong_out_Function(System.UInt64&)' called when client was not active"); + } + behaviour.Client_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_Vector3_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'UnityEngine.Vector3 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_Vector3_Function()' called when client was not active"); + } + Vector3 actual = behaviour.Client_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_Vector3_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_Vector3_out_Function(UnityEngine.Vector3&)' called when client was not active"); + } + behaviour.Client_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'Mirror.Tests.Generated.Attributes.ClassWithNoConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_ClassWithNoConstructor_Function()' called when client was not active"); + } + ClassWithNoConstructor actual = behaviour.Client_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_ClassWithNoConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithNoConstructor&)' called when client was not active"); + } + behaviour.Client_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'Mirror.Tests.Generated.Attributes.ClassWithConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_ClassWithConstructor_Function()' called when client was not active"); + } + ClassWithConstructor actual = behaviour.Client_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Client_ClassWithConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Client] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Client_ClassWithConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithConstructor&)' called when client was not active"); + } + behaviour.Client_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_float_returnsValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Single Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_float_Function()' called when server was not active"); + } + float actual = behaviour.Server_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_float_setsOutValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_float_out_Function(System.Single&)' called when server was not active"); + } + behaviour.Server_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_double_returnsValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Double Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_double_Function()' called when server was not active"); + } + double actual = behaviour.Server_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_double_setsOutValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_double_out_Function(System.Double&)' called when server was not active"); + } + behaviour.Server_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_bool_returnsValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Boolean Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_bool_Function()' called when server was not active"); + } + bool actual = behaviour.Server_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_bool_setsOutValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_bool_out_Function(System.Boolean&)' called when server was not active"); + } + behaviour.Server_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_char_returnsValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Char Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_char_Function()' called when server was not active"); + } + char actual = behaviour.Server_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_char_setsOutValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_char_out_Function(System.Char&)' called when server was not active"); + } + behaviour.Server_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_byte_returnsValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Byte Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_byte_Function()' called when server was not active"); + } + byte actual = behaviour.Server_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_byte_setsOutValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_byte_out_Function(System.Byte&)' called when server was not active"); + } + behaviour.Server_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_int_returnsValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Int32 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_int_Function()' called when server was not active"); + } + int actual = behaviour.Server_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_int_setsOutValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_int_out_Function(System.Int32&)' called when server was not active"); + } + behaviour.Server_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_long_returnsValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Int64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_long_Function()' called when server was not active"); + } + long actual = behaviour.Server_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_long_setsOutValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_long_out_Function(System.Int64&)' called when server was not active"); + } + behaviour.Server_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ulong_returnsValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.UInt64 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_ulong_Function()' called when server was not active"); + } + ulong actual = behaviour.Server_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ulong_setsOutValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_ulong_out_Function(System.UInt64&)' called when server was not active"); + } + behaviour.Server_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_Vector3_returnsValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'UnityEngine.Vector3 Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_Vector3_Function()' called when server was not active"); + } + Vector3 actual = behaviour.Server_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_Vector3_setsOutValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_Vector3_out_Function(UnityEngine.Vector3&)' called when server was not active"); + } + behaviour.Server_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'Mirror.Tests.Generated.Attributes.ClassWithNoConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_ClassWithNoConstructor_Function()' called when server was not active"); + } + ClassWithNoConstructor actual = behaviour.Server_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_ClassWithNoConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithNoConstructor&)' called when server was not active"); + } + behaviour.Server_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'Mirror.Tests.Generated.Attributes.ClassWithConstructor Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_ClassWithConstructor_Function()' called when server was not active"); + } + ClassWithConstructor actual = behaviour.Server_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Server_ClassWithConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + if (!active) + { + LogAssert.Expect(LogType.Warning, "[Server] function 'System.Void Mirror.Tests.Generated.Attributes.AttributeBehaviour_ClassWithNoConstructor::Server_ClassWithConstructor_out_Function(Mirror.Tests.Generated.Attributes.ClassWithConstructor&)' called when server was not active"); + } + behaviour.Server_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_float_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + float actual = behaviour.ClientCallback_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_float_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + behaviour.ClientCallback_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_double_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + double actual = behaviour.ClientCallback_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_double_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + behaviour.ClientCallback_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_bool_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + bool actual = behaviour.ClientCallback_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_bool_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + behaviour.ClientCallback_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_char_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + char actual = behaviour.ClientCallback_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_char_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + behaviour.ClientCallback_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_byte_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + byte actual = behaviour.ClientCallback_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_byte_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + behaviour.ClientCallback_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_int_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + int actual = behaviour.ClientCallback_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_int_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + behaviour.ClientCallback_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_long_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + long actual = behaviour.ClientCallback_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_long_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + behaviour.ClientCallback_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ulong_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + ulong actual = behaviour.ClientCallback_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ulong_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + behaviour.ClientCallback_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_Vector3_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + Vector3 actual = behaviour.ClientCallback_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_Vector3_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + behaviour.ClientCallback_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + ClassWithNoConstructor actual = behaviour.ClientCallback_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + behaviour.ClientCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithConstructor_returnsValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + ClassWithConstructor actual = behaviour.ClientCallback_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ClientCallback_ClassWithConstructor_setsOutValue(bool active) + { + NetworkClient.connectState = active ? ConnectState.Connected : ConnectState.None; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + behaviour.ClientCallback_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_float_returnsValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + float actual = behaviour.ServerCallback_float_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_float_setsOutValue(bool active) + { + NetworkServer.active = active; + + float expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_float : default; + + behaviour.ServerCallback_float_out_Function(out float actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_double_returnsValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + double actual = behaviour.ServerCallback_double_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_double_setsOutValue(bool active) + { + NetworkServer.active = active; + + double expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_double : default; + + behaviour.ServerCallback_double_out_Function(out double actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_bool_returnsValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + bool actual = behaviour.ServerCallback_bool_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_bool_setsOutValue(bool active) + { + NetworkServer.active = active; + + bool expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_bool : default; + + behaviour.ServerCallback_bool_out_Function(out bool actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_char_returnsValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + char actual = behaviour.ServerCallback_char_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_char_setsOutValue(bool active) + { + NetworkServer.active = active; + + char expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_char : default; + + behaviour.ServerCallback_char_out_Function(out char actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_byte_returnsValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + byte actual = behaviour.ServerCallback_byte_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_byte_setsOutValue(bool active) + { + NetworkServer.active = active; + + byte expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_byte : default; + + behaviour.ServerCallback_byte_out_Function(out byte actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_int_returnsValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + int actual = behaviour.ServerCallback_int_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_int_setsOutValue(bool active) + { + NetworkServer.active = active; + + int expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_int : default; + + behaviour.ServerCallback_int_out_Function(out int actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_long_returnsValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + long actual = behaviour.ServerCallback_long_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_long_setsOutValue(bool active) + { + NetworkServer.active = active; + + long expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_long : default; + + behaviour.ServerCallback_long_out_Function(out long actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ulong_returnsValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + ulong actual = behaviour.ServerCallback_ulong_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ulong_setsOutValue(bool active) + { + NetworkServer.active = active; + + ulong expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ulong : default; + + behaviour.ServerCallback_ulong_out_Function(out ulong actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_Vector3_returnsValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + Vector3 actual = behaviour.ServerCallback_Vector3_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_Vector3_setsOutValue(bool active) + { + NetworkServer.active = active; + + Vector3 expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_Vector3 : default; + + behaviour.ServerCallback_Vector3_out_Function(out Vector3 actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithNoConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + ClassWithNoConstructor actual = behaviour.ServerCallback_ClassWithNoConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithNoConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithNoConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithNoConstructor : default; + + behaviour.ServerCallback_ClassWithNoConstructor_out_Function(out ClassWithNoConstructor actual); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithConstructor_returnsValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + ClassWithConstructor actual = behaviour.ServerCallback_ClassWithConstructor_Function(); + + Assert.AreEqual(expected, actual); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void ServerCallback_ClassWithConstructor_setsOutValue(bool active) + { + NetworkServer.active = active; + + ClassWithConstructor expected = active ? AttributeBehaviour_ClassWithNoConstructor.Expected_ClassWithConstructor : default; + + behaviour.ServerCallback_ClassWithConstructor_out_Function(out ClassWithConstructor actual); + + Assert.AreEqual(expected, actual); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs.meta b/Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs.meta new file mode 100644 index 000000000..3e05bedb0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Generated/AttritubeTest.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e49c298f22d4292439dc17f7e59f08b7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs b/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs new file mode 100644 index 000000000..081b7f4c0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs @@ -0,0 +1,1049 @@ +// Generated by CollectionWriterGenerator.cs +using System; +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.Generated.CollectionWriters +{ + public struct FloatStringStruct + { + public float value; + public string anotherValue; + } + + public class ClassWithNoConstructor + { + public int a; + } + + public class Array_int_Test + { + public struct Message : NetworkMessage + { + public int[] collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + int[] unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new int[] {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + int[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new int[] + { + 3, 4, 5 + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + int[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(3)); + Assert.That(unpackedCollection[1], Is.EqualTo(4)); + Assert.That(unpackedCollection[2], Is.EqualTo(5)); + } + } + + public class Array_string_Test + { + public struct Message : NetworkMessage + { + public string[] collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + string[] unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new string[] {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + string[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new string[] + { + "Some", "String", "Value" + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + string[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo("Some")); + Assert.That(unpackedCollection[1], Is.EqualTo("String")); + Assert.That(unpackedCollection[2], Is.EqualTo("Value")); + } + } + + public class Array_Vector3_Test + { + public struct Message : NetworkMessage + { + public Vector3[] collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + Vector3[] unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new Vector3[] {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + Vector3[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new Vector3[] + { + new Vector3(1, 2, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9) + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + Vector3[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(new Vector3(1, 2, 3))); + Assert.That(unpackedCollection[1], Is.EqualTo(new Vector3(4, 5, 6))); + Assert.That(unpackedCollection[2], Is.EqualTo(new Vector3(7, 8, 9))); + } + } + + public class Array_FloatStringStruct_Test + { + public struct Message : NetworkMessage + { + public FloatStringStruct[] collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + FloatStringStruct[] unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new FloatStringStruct[] {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + FloatStringStruct[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new FloatStringStruct[] + { + new FloatStringStruct { value = 3, anotherValue = "Some" }, new FloatStringStruct { value = 4, anotherValue = "String" }, new FloatStringStruct { value = 5, anotherValue = "Values" } + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + FloatStringStruct[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(new FloatStringStruct { value = 3, anotherValue = "Some" })); + Assert.That(unpackedCollection[1], Is.EqualTo(new FloatStringStruct { value = 4, anotherValue = "String" })); + Assert.That(unpackedCollection[2], Is.EqualTo(new FloatStringStruct { value = 5, anotherValue = "Values" })); + } + } + + public class Array_ClassWithNoConstructor_Test + { + public struct Message : NetworkMessage + { + public ClassWithNoConstructor[] collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ClassWithNoConstructor[] unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new ClassWithNoConstructor[] {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ClassWithNoConstructor[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new ClassWithNoConstructor[] + { + new ClassWithNoConstructor { a = 3 }, new ClassWithNoConstructor { a = 4 }, new ClassWithNoConstructor { a = 5 } + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ClassWithNoConstructor[] unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0].a, Is.EqualTo(new ClassWithNoConstructor { a = 3 }.a)); + Assert.That(unpackedCollection[1].a, Is.EqualTo(new ClassWithNoConstructor { a = 4 }.a)); + Assert.That(unpackedCollection[2].a, Is.EqualTo(new ClassWithNoConstructor { a = 5 }.a)); + } + } + + public class ArraySegment_int_Test + { + public struct Message : NetworkMessage + { + public ArraySegment collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection.Array, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + int[] array = new int[] + { + default, + default, + default, + }; + + Message message = new Message + { + collection = new ArraySegment(array, 0, 0) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsEmpty(unpackedCollection.Array); + } + + [Test] + public void SendsData() + { + int[] array = new int[] + { + default, + 3, 4, 5, + default, + default, + default, + }; + + + Message message = new Message + { + collection = new ArraySegment(array, 1, 3) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsNotEmpty(unpackedCollection.Array); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 0], Is.EqualTo(3)); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 1], Is.EqualTo(4)); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 2], Is.EqualTo(5)); + } + } + + public class ArraySegment_string_Test + { + public struct Message : NetworkMessage + { + public ArraySegment collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection.Array, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + string[] array = new string[] + { + default, + default, + default, + }; + + Message message = new Message + { + collection = new ArraySegment(array, 0, 0) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsEmpty(unpackedCollection.Array); + } + + [Test] + public void SendsData() + { + string[] array = new string[] + { + default, + "Some", "String", "Value", + default, + default, + default, + }; + + + Message message = new Message + { + collection = new ArraySegment(array, 1, 3) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsNotEmpty(unpackedCollection.Array); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 0], Is.EqualTo("Some")); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 1], Is.EqualTo("String")); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 2], Is.EqualTo("Value")); + } + } + + public class ArraySegment_Vector3_Test + { + public struct Message : NetworkMessage + { + public ArraySegment collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection.Array, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Vector3[] array = new Vector3[] + { + default, + default, + default, + }; + + Message message = new Message + { + collection = new ArraySegment(array, 0, 0) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsEmpty(unpackedCollection.Array); + } + + [Test] + public void SendsData() + { + Vector3[] array = new Vector3[] + { + default, + new Vector3(1, 2, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9), + default, + default, + default, + }; + + + Message message = new Message + { + collection = new ArraySegment(array, 1, 3) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsNotEmpty(unpackedCollection.Array); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 0], Is.EqualTo(new Vector3(1, 2, 3))); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 1], Is.EqualTo(new Vector3(4, 5, 6))); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 2], Is.EqualTo(new Vector3(7, 8, 9))); + } + } + + public class ArraySegment_FloatStringStruct_Test + { + public struct Message : NetworkMessage + { + public ArraySegment collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection.Array, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + FloatStringStruct[] array = new FloatStringStruct[] + { + default, + default, + default, + }; + + Message message = new Message + { + collection = new ArraySegment(array, 0, 0) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsEmpty(unpackedCollection.Array); + } + + [Test] + public void SendsData() + { + FloatStringStruct[] array = new FloatStringStruct[] + { + default, + new FloatStringStruct { value = 3, anotherValue = "Some" }, new FloatStringStruct { value = 4, anotherValue = "String" }, new FloatStringStruct { value = 5, anotherValue = "Values" }, + default, + default, + default, + }; + + + Message message = new Message + { + collection = new ArraySegment(array, 1, 3) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsNotEmpty(unpackedCollection.Array); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 0], Is.EqualTo(new FloatStringStruct { value = 3, anotherValue = "Some" })); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 1], Is.EqualTo(new FloatStringStruct { value = 4, anotherValue = "String" })); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 2], Is.EqualTo(new FloatStringStruct { value = 5, anotherValue = "Values" })); + } + } + + public class ArraySegment_ClassWithNoConstructor_Test + { + public struct Message : NetworkMessage + { + public ArraySegment collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection.Array, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + ClassWithNoConstructor[] array = new ClassWithNoConstructor[] + { + default, + default, + default, + }; + + Message message = new Message + { + collection = new ArraySegment(array, 0, 0) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsEmpty(unpackedCollection.Array); + } + + [Test] + public void SendsData() + { + ClassWithNoConstructor[] array = new ClassWithNoConstructor[] + { + default, + new ClassWithNoConstructor { a = 3 }, new ClassWithNoConstructor { a = 4 }, new ClassWithNoConstructor { a = 5 }, + default, + default, + default, + }; + + + Message message = new Message + { + collection = new ArraySegment(array, 1, 3) + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + ArraySegment unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection.Array); + Assert.IsNotEmpty(unpackedCollection.Array); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 0].a, Is.EqualTo(new ClassWithNoConstructor { a = 3 }.a)); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 1].a, Is.EqualTo(new ClassWithNoConstructor { a = 4 }.a)); + Assert.That(unpackedCollection.Array[unpackedCollection.Offset + 2].a, Is.EqualTo(new ClassWithNoConstructor { a = 5 }.a)); + } + } + + public class List_int_Test + { + public struct Message : NetworkMessage + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + 3, 4, 5 + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(3)); + Assert.That(unpackedCollection[1], Is.EqualTo(4)); + Assert.That(unpackedCollection[2], Is.EqualTo(5)); + } + } + + public class List_string_Test + { + public struct Message : NetworkMessage + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + "Some", "String", "Value" + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo("Some")); + Assert.That(unpackedCollection[1], Is.EqualTo("String")); + Assert.That(unpackedCollection[2], Is.EqualTo("Value")); + } + } + + public class List_Vector3_Test + { + public struct Message : NetworkMessage + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + new Vector3(1, 2, 3), new Vector3(4, 5, 6), new Vector3(7, 8, 9) + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(new Vector3(1, 2, 3))); + Assert.That(unpackedCollection[1], Is.EqualTo(new Vector3(4, 5, 6))); + Assert.That(unpackedCollection[2], Is.EqualTo(new Vector3(7, 8, 9))); + } + } + + public class List_FloatStringStruct_Test + { + public struct Message : NetworkMessage + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + new FloatStringStruct { value = 3, anotherValue = "Some" }, new FloatStringStruct { value = 4, anotherValue = "String" }, new FloatStringStruct { value = 5, anotherValue = "Values" } + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0], Is.EqualTo(new FloatStringStruct { value = 3, anotherValue = "Some" })); + Assert.That(unpackedCollection[1], Is.EqualTo(new FloatStringStruct { value = 4, anotherValue = "String" })); + Assert.That(unpackedCollection[2], Is.EqualTo(new FloatStringStruct { value = 5, anotherValue = "Values" })); + } + } + + public class List_ClassWithNoConstructor_Test + { + public struct Message : NetworkMessage + { + public List collection; + } + + [Test] + public void SendsNull() + { + Message message = new Message + { + collection = default + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.That(unpackedCollection, Is.Null.Or.Empty); + } + + [Test] + public void SendsEmpty() + { + Message message = new Message + { + collection = new List {} + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsEmpty(unpackedCollection); + } + + [Test] + public void SendsData() + { + Message message = new Message + { + collection = new List + { + new ClassWithNoConstructor { a = 3 }, new ClassWithNoConstructor { a = 4 }, new ClassWithNoConstructor { a = 5 } + } + }; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + Message unpacked = MessagePackingTest.UnpackFromByteArray(data); + List unpackedCollection = unpacked.collection; + + Assert.IsNotNull(unpackedCollection); + Assert.IsNotEmpty(unpackedCollection); + Assert.That(unpackedCollection[0].a, Is.EqualTo(new ClassWithNoConstructor { a = 3 }.a)); + Assert.That(unpackedCollection[1].a, Is.EqualTo(new ClassWithNoConstructor { a = 4 }.a)); + Assert.That(unpackedCollection[2].a, Is.EqualTo(new ClassWithNoConstructor { a = 5 }.a)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs.meta b/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs.meta new file mode 100644 index 000000000..28a873611 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Generated/CollectionWriterTests.gen.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c573b90d8949dec4cafb4f7401be9950 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Grid2DTests.cs b/Assets/Mirror/Tests/Editor/Grid2DTests.cs new file mode 100644 index 000000000..b3a608542 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Grid2DTests.cs @@ -0,0 +1,58 @@ +using System.Collections.Generic; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class Grid2DTests + { + Grid2D grid = new Grid2D(); + + [Test] + public void AddAndGetNeighbours() + { + // add two at (0, 0) + grid.Add(Vector2Int.zero, 1); + grid.Add(Vector2Int.zero, 2); + HashSet result = new HashSet(); + grid.GetWithNeighbours(Vector2Int.zero, result); + Assert.That(result.Count, Is.EqualTo(2)); + Assert.That(result.Contains(1), Is.True); + Assert.That(result.Contains(2), Is.True); + + // add a neighbour at (1, 1) + grid.Add(new Vector2Int(1, 1), 3); + grid.GetWithNeighbours(Vector2Int.zero, result); + Assert.That(result.Count, Is.EqualTo(3)); + Assert.That(result.Contains(1), Is.True); + Assert.That(result.Contains(2), Is.True); + Assert.That(result.Contains(3), Is.True); + } + + [Test] + public void GetIgnoresTooFarNeighbours() + { + // add at (0, 0) + grid.Add(Vector2Int.zero, 1); + + // get at (2, 0) which is out of 9 neighbour radius + HashSet result = new HashSet(); + grid.GetWithNeighbours(new Vector2Int(2, 0), result); + Assert.That(result.Count, Is.EqualTo(0)); + } + + [Test] + public void ClearNonAlloc() + { + // add some + grid.Add(Vector2Int.zero, 1); + grid.Add(Vector2Int.zero, 2); + + // clear and check if empty now + grid.ClearNonAlloc(); + HashSet result = new HashSet(); + grid.GetWithNeighbours(Vector2Int.zero, result); + Assert.That(result.Count, Is.EqualTo(0)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Grid2DTests.cs.meta b/Assets/Mirror/Tests/Editor/Grid2DTests.cs.meta new file mode 100644 index 000000000..2f71bbdc1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Grid2DTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f8f3c4f37bb54824b5dfe70e0984b3d3 +timeCreated: 1613188745 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs b/Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs new file mode 100644 index 000000000..8b57a8966 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs @@ -0,0 +1,95 @@ +// default = no component = everyone sees everyone +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public abstract class InterestManagementTests_Common : MirrorEditModeTest + { + protected GameObject gameObjectA; + protected NetworkIdentity identityA; + protected NetworkConnectionToClient connectionA; + + protected GameObject gameObjectB; + protected NetworkIdentity identityB; + protected NetworkConnectionToClient connectionB; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // A with connectionId = 0x0A, netId = 0xAA + CreateNetworked(out gameObjectA, out identityA); + connectionA = new NetworkConnectionToClient(0x0A); + connectionA.isAuthenticated = true; + connectionA.isReady = true; + connectionA.identity = identityA; + NetworkServer.spawned[0xAA] = identityA; + + // B + CreateNetworked(out gameObjectB, out identityB); + connectionB = new NetworkConnectionToClient(0x0B); + connectionB.isAuthenticated = true; + connectionB.isReady = true; + connectionB.identity = identityB; + NetworkServer.spawned[0xBB] = identityB; + + // need to start server so that interest management works + NetworkServer.Listen(10); + + // add both connections + NetworkServer.connections[connectionA.connectionId] = connectionA; + NetworkServer.connections[connectionB.connectionId] = connectionB; + + // spawn both so that .observers is created + NetworkServer.Spawn(gameObjectA, connectionA); + NetworkServer.Spawn(gameObjectB, connectionB); + + // spawn already runs interest management once + // clear observers and observing so tests can start from scratch + identityA.observers.Clear(); + identityB.observers.Clear(); + connectionA.observing.Clear(); + connectionB.observing.Clear(); + } + + [TearDown] + public override void TearDown() + { + // set isServer is false. otherwise Destroy instead of + // DestroyImmediate is called internally, giving an error in Editor + identityA.isServer = false; + + // set isServer is false. otherwise Destroy instead of + // DestroyImmediate is called internally, giving an error in Editor + identityB.isServer = false; + + // clear connections first. calling OnDisconnect wouldn't work since + // we have no real clients. + NetworkServer.connections.Clear(); + + base.TearDown(); + } + + // player should always see self no matter what + [Test] + public void PlayerAlwaysSeesSelf_Initial() + { + // rebuild for A + // initial rebuild adds all connections if no interest management available + NetworkServer.RebuildObservers(identityA, true); + + // should see self + Assert.That(identityA.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // forceHidden should still work + [Test] + public abstract void ForceHidden_Initial(); + + // forceShown should still work + [Test] + public abstract void ForceShown_Initial(); + } +} diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs.meta b/Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs.meta new file mode 100644 index 000000000..fa5a3884a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_Common.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: f0349d2e3a74454e9dc8badec1db0289 +timeCreated: 1613049868 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs b/Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs new file mode 100644 index 000000000..f33505d40 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs @@ -0,0 +1,62 @@ +// default = no component = everyone sees everyone +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class InterestManagementTests_Default : InterestManagementTests_Common + { + // no interest management (default) + // => forceHidden should still work + [Test] + public override void ForceHidden_Initial() + { + // force hide A + identityA.visible = Visibility.ForceHidden; + + // rebuild for both + // initial rebuild adds all connections if no interest management available + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // A should not be seen by B because A is force hidden + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + // B should be seen by A because + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // no interest management (default) + // => forceShown should still work + [Test] + public override void ForceShown_Initial() + { + // force show A + identityA.visible = Visibility.ForceShown; + + // rebuild for both + // initial rebuild adds all connections if no interest management available + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // both should see each other because by default, everyone sees everyone + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // no interest management (default) + // => everyone should see everyone + [Test] + public void EveryoneSeesEveryone_Initial() + { + // rebuild for both + // initial rebuild adds all connections if no interest management available + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // both should see each other + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // TODO add tests to make sure old observers are removed etc. + } +} diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs.meta b/Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs.meta new file mode 100644 index 000000000..96ed6142a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_Default.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: c5ac1fc2c25043378f80bc02d868a5d6 +timeCreated: 1613042576 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs b/Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs new file mode 100644 index 000000000..9d8cf35a6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs @@ -0,0 +1,142 @@ +// Vector3.Distance based +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class InterestManagementTests_Distance : InterestManagementTests_Common + { + DistanceInterestManagement aoi; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // create AOI GameObject + CreateGameObject(out GameObject go, out aoi); + aoi.visRange = 10; + // setup server aoi since InterestManagement Awake isn't called + NetworkServer.aoi = aoi; + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + // clear server aoi again + NetworkServer.aoi = null; + } + + // brute force interest management + // => forceHidden should still work + [Test] + public override void ForceHidden_Initial() + { + // A and B are at (0,0,0) so within range! + + // force hide A + identityA.visible = Visibility.ForceHidden; + + // rebuild for both + // initial rebuild while both are within range + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // A should not be seen by B because A is force hidden + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + // B should be seen by A because + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // brute force interest management + // => forceHidden should still work + [Test] + public override void ForceShown_Initial() + { + // A and B are too far from each other + identityB.transform.position = Vector3.right * (aoi.visRange + 1); + + // force show A + identityA.visible = Visibility.ForceShown; + + // rebuild for both + // initial rebuild while both are within range + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // A should see B because A is force shown + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + // B should not be seen by A because they are too far from each other + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.False); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void InRange_Initial() + { + // A and B are at (0,0,0) so within range! + + // rebuild for both + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // both should see each other because they are in range + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void InRange_NotInitial() + { + // A and B are at (0,0,0) so within range! + + // rebuild for both + NetworkServer.RebuildObservers(identityA, false); + NetworkServer.RebuildObservers(identityB, false); + + // both should see each other because they are in range + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void OutOfRange_Initial() + { + // A and B are too far from each other + identityB.transform.position = Vector3.right * (aoi.visRange + 1); + + // rebuild for boths + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // both should not see each other + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.False); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void OutOfRange_NotInitial() + { + // A and B are too far from each other + identityB.transform.position = Vector3.right * (aoi.visRange + 1); + + // rebuild for boths + NetworkServer.RebuildObservers(identityA, false); + NetworkServer.RebuildObservers(identityB, false); + + // both should not see each other + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.False); + } + + // TODO add tests to make sure old observers are removed etc. + } +} diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs.meta b/Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs.meta new file mode 100644 index 000000000..269736062 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_Distance.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ffb9dc0ff01e4f979c216984d7fc48d0 +timeCreated: 1613049838 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs b/Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs new file mode 100644 index 000000000..78a3b9090 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs @@ -0,0 +1,154 @@ +// default = no component = everyone sees everyone +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class InterestManagementTests_SpatialHashing : InterestManagementTests_Common + { + SpatialHashingInterestManagement aoi; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // create spatial hashing object & component + CreateGameObject(out GameObject go, out aoi); + aoi.visRange = 10; + // setup server aoi since InterestManagement Awake isn't called + NetworkServer.aoi = aoi; + + // rebuild grid once so the two connections are in there + aoi.Update(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + // clear server aoi again + NetworkServer.aoi = null; + } + + // brute force interest management + // => forceHidden should still work + [Test] + public override void ForceHidden_Initial() + { + // A and B are at (0,0,0) so within range! + + // force hide A + identityA.visible = Visibility.ForceHidden; + + // rebuild for both + // initial rebuild while both are within range + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // A should not be seen by B because A is force hidden + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + // B should be seen by A because + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // brute force interest management + // => forceShown should still work + [Test] + public override void ForceShown_Initial() + { + // A and B are too far from each other + identityB.transform.position = Vector3.right * (aoi.visRange + 1); + + // force show A + identityA.visible = Visibility.ForceShown; + + // update grid now that positions were changed + aoi.Update(); + + // rebuild for both + // initial rebuild while both are within range + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // A should see B because A is force shown + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + // B should not be seen by A because they are too far from each other + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.False); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void InRange_Initial() + { + // A and B are at (0,0,0) so within range! + + // rebuild for both + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // both should see each other because they are in range + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void InRange_NotInitial() + { + // A and B are at (0,0,0) so within range! + + // rebuild for both + NetworkServer.RebuildObservers(identityA, false); + NetworkServer.RebuildObservers(identityB, false); + + // both should see each other because they are in range + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.True); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.True); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void OutOfRange_Initial() + { + // A and B are too far from each other + identityB.transform.position = Vector3.right * (aoi.visRange + 1); + + // update grid now that positions were changed + aoi.Update(); + + // rebuild for boths + NetworkServer.RebuildObservers(identityA, true); + NetworkServer.RebuildObservers(identityB, true); + + // both should not see each other + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.False); + } + + // brute force interest management + // => everyone should see everyone if in range + [Test] + public void OutOfRange_NotInitial() + { + // A and B are too far from each other + identityB.transform.position = Vector3.right * (aoi.visRange + 1); + + // update grid now that positions were changed + aoi.Update(); + + // rebuild for boths + NetworkServer.RebuildObservers(identityA, false); + NetworkServer.RebuildObservers(identityB, false); + + // both should not see each other + Assert.That(identityA.observers.ContainsKey(connectionB.connectionId), Is.False); + Assert.That(identityB.observers.ContainsKey(connectionA.connectionId), Is.False); + } + + // TODO add tests to make sure old observers are removed etc. + } +} diff --git a/Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs.meta b/Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs.meta new file mode 100644 index 000000000..8f145f0a4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/InterestManagementTests_SpatialHashing.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 63d9e0d6aa9a4eec8b4e2db07e6261bf +timeCreated: 1613117841 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/LocalConnectionTest.cs b/Assets/Mirror/Tests/Editor/LocalConnectionTest.cs new file mode 100644 index 000000000..9fe986b94 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/LocalConnectionTest.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class LocalConnectionTest : MirrorTest + { + struct TestMessage : NetworkMessage {} + + LocalConnectionToClient connectionToClient; + LocalConnectionToServer connectionToServer; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + CreateLocalConnectionPair(out connectionToClient, out connectionToServer); + + // set up server/client connections so message handling works + NetworkClient.connection = connectionToServer; + NetworkServer.connections[connectionToClient.connectionId] = connectionToClient; + } + + [TearDown] + public override void TearDown() + { + connectionToServer.Disconnect(); + base.TearDown(); + } + + [Test] + public void ClientToServerTest() + { + Assert.That(connectionToClient.address, Is.EqualTo("localhost")); + + bool invoked = false; + void Handler(NetworkConnection conn, TestMessage message) + { + invoked = true; + } + + // set up handler on the server connection + NetworkServer.RegisterHandler(Handler, false); + + connectionToServer.Send(new TestMessage()); + connectionToServer.Update(); + + Assert.True(invoked, "handler should have been invoked"); + } + + [Test] + public void ServerToClient() + { + Assert.That(connectionToServer.address, Is.EqualTo("localhost")); + + bool invoked = false; + void Handler(TestMessage message) + { + invoked = true; + } + + // set up handler on the client connection + NetworkClient.RegisterHandler(Handler, false); + + connectionToClient.Send(new TestMessage()); + connectionToServer.Update(); + + Assert.True(invoked, "handler should have been invoked"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/LocalConnectionTest.cs.meta b/Assets/Mirror/Tests/Editor/LocalConnectionTest.cs.meta new file mode 100644 index 000000000..a1a525792 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/LocalConnectionTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 405841e2a21c64d7585d5c71d06ffff2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs b/Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs new file mode 100644 index 000000000..89a84b7a5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs @@ -0,0 +1,125 @@ +// TODO Send only supports structs. Consider removing those tests. +using NUnit.Framework; + +namespace Mirror.Tests.MessageTests +{ + class ParentMessage : NetworkMessage + { + public int parentValue; + } + + class ChildMessage : ParentMessage + { + public int childValue; + } + + + public abstract class RequestMessageBase : NetworkMessage + { + public int responseId = 0; + } + public class ResponseMessage : RequestMessageBase + { + public int state; + public string message = ""; + public int errorCode = 0; // optional for error codes + } + + //reverseOrder to test this https://github.com/vis2k/Mirror/issues/1925 + public class ResponseMessageReverse : RequestMessageBaseReverse + { + public int state; + public string message = ""; + public int errorCode = 0; // optional for error codes + } + public abstract class RequestMessageBaseReverse : NetworkMessage + { + public int responseId = 0; + } + + [TestFixture] + public class MessageInheritanceTest + { + [Test] + public void SendsVauesInParentAndChildClass() + { + NetworkWriter writer = new NetworkWriter(); + + writer.Write(new ChildMessage + { + parentValue = 3, + childValue = 4 + }); + + byte[] arr = writer.ToArray(); + + NetworkReader reader = new NetworkReader(arr); + ChildMessage received = reader.Read(); + + Assert.AreEqual(3, received.parentValue); + Assert.AreEqual(4, received.childValue); + + int writeLength = writer.Position; + int readLength = reader.Position; + Assert.That(writeLength == readLength, $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}"); + } + + [Test] + public void SendsVauesWhenUsingAbstractClass() + { + NetworkWriter writer = new NetworkWriter(); + + const int state = 2; + const string message = "hello world"; + const int responseId = 5; + writer.Write(new ResponseMessage + { + state = state, + message = message, + responseId = responseId, + }); + + byte[] arr = writer.ToArray(); + + NetworkReader reader = new NetworkReader(arr); + ResponseMessage received = reader.Read(); + + Assert.AreEqual(state, received.state); + Assert.AreEqual(message, received.message); + Assert.AreEqual(responseId, received.responseId); + + int writeLength = writer.Position; + int readLength = reader.Position; + Assert.That(writeLength == readLength, $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}"); + } + + [Test] + public void SendsVauesWhenUsingAbstractClassReverseDefineOrder() + { + NetworkWriter writer = new NetworkWriter(); + + const int state = 2; + const string message = "hello world"; + const int responseId = 5; + writer.Write(new ResponseMessageReverse + { + state = state, + message = message, + responseId = responseId, + }); + + byte[] arr = writer.ToArray(); + + NetworkReader reader = new NetworkReader(arr); + ResponseMessageReverse received = reader.Read(); + + Assert.AreEqual(state, received.state); + Assert.AreEqual(message, received.message); + Assert.AreEqual(responseId, received.responseId); + + int writeLength = writer.Position; + int readLength = reader.Position; + Assert.That(writeLength == readLength, $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs.meta b/Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs.meta new file mode 100644 index 000000000..4e78acae8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MessageInheritanceTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 808855b645f9843d2b3077ab1304b2b3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/MessagePackingTest.cs b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs new file mode 100644 index 000000000..570edb435 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs @@ -0,0 +1,143 @@ +using System; +using Mirror.Tests.MessageTests; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + [TestFixture] + public class MessagePackingTest + { + public struct EmptyMessage : NetworkMessage {} + + // helper function to pack message into a simple byte[] + public static byte[] PackToByteArray(T message) + where T : struct, NetworkMessage + { + using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + { + MessagePacking.Pack(message, writer); + return writer.ToArray(); + } + } + + // unpack a message we received + public static T UnpackFromByteArray(byte[] data) + where T : struct, NetworkMessage + { + using (PooledNetworkReader networkReader = NetworkReaderPool.GetReader(data)) + { + int msgType = MessagePacking.GetId(); + + int id = networkReader.ReadUShort(); + if (id != msgType) + throw new FormatException($"Invalid message, could not unpack {typeof(T).FullName}"); + + return networkReader.Read(); + } + } + + // message id is generated from message.FullName. + // should be consistent across all platforms. + [Test] + public void GetId() + { + // "Mirror.Tests.MessageTests.TestMessage" + Debug.Log(typeof(TestMessage).FullName); + Assert.That(MessagePacking.GetId(), Is.EqualTo(0x8706)); + } + + [Test] + public void TestPacking() + { + SceneMessage message = new SceneMessage() + { + sceneName = "Hello world", + sceneOperation = SceneOperation.LoadAdditive + }; + + byte[] data = PackToByteArray(message); + + SceneMessage unpacked = UnpackFromByteArray(data); + + Assert.That(unpacked.sceneName, Is.EqualTo("Hello world")); + Assert.That(unpacked.sceneOperation, Is.EqualTo(SceneOperation.LoadAdditive)); + } + + [Test] + public void UnpackWrongMessage() + { + SpawnMessage message = new SpawnMessage(); + + byte[] data = PackToByteArray(message); + + Assert.Throws(() => + { + ReadyMessage unpacked = UnpackFromByteArray(data); + }); + } + + [Test] + public void TestUnpackIdMismatch() + { + // Unpack has a id != msgType case that throws a FormatException. + // let's try to trigger it. + + SceneMessage message = new SceneMessage() + { + sceneName = "Hello world", + sceneOperation = SceneOperation.LoadAdditive + }; + + byte[] data = PackToByteArray(message); + + // overwrite the id + data[0] = 0x01; + data[1] = 0x02; + + Assert.Throws(() => + { + SceneMessage unpacked = UnpackFromByteArray(data); + }); + } + + [Test] + public void TestUnpackMessageNonGeneric() + { + // try a regular message + SceneMessage message = new SceneMessage() + { + sceneName = "Hello world", + sceneOperation = SceneOperation.LoadAdditive + }; + + byte[] data = PackToByteArray(message); + NetworkReader reader = new NetworkReader(data); + + bool result = MessagePacking.Unpack(reader, out ushort msgType); + Assert.That(result, Is.EqualTo(true)); + Assert.That(msgType, Is.EqualTo(BitConverter.ToUInt16(data, 0))); + } + + [Test] + public void UnpackInvalidMessage() + { + // try an invalid message + NetworkReader reader2 = new NetworkReader(new byte[0]); + bool result2 = MessagePacking.Unpack(reader2, out ushort msgType2); + Assert.That(result2, Is.EqualTo(false)); + Assert.That(msgType2, Is.EqualTo(0)); + } + + [Test] + public void MessageIdIsCorrectLength() + { + NetworkWriter writer = new NetworkWriter(); + MessagePacking.Pack(new EmptyMessage(), writer); + + ArraySegment segment = writer.ToArraySegment(); + + Assert.That(segment.Count, Is.EqualTo(MessagePacking.HeaderSize), "Empty message should have same size as HeaderSize"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/MessagePackingTest.cs.meta b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs.meta new file mode 100644 index 000000000..70e62403b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MessagePackingTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8d57c17d9ee7c49e6bacc54ddbeac751 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs b/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs new file mode 100644 index 000000000..bb24fd8d9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs @@ -0,0 +1,386 @@ +using System; +using System.IO; +using NSubstitute; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class MyMiddleware : MiddlewareTransport {} + + [Description("Test to make sure inner methods are called when using Middleware Transport")] + public class MiddlewareTransportTest + { + Transport inner; + MyMiddleware middleware; + + [SetUp] + public void Setup() + { + inner = Substitute.For(); + + GameObject gameObject = new GameObject(); + + middleware = gameObject.AddComponent(); + middleware.inner = inner; + } + + [TearDown] + public void TearDown() + { + GameObject.DestroyImmediate(middleware.gameObject); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestAvailable(bool available) + { + inner.Available().Returns(available); + + Assert.That(middleware.Available(), Is.EqualTo(available)); + + inner.Received(1).Available(); + } + + [Test] + [TestCase(Channels.Reliable, 4000)] + [TestCase(Channels.Reliable, 2000)] + [TestCase(Channels.Unreliable, 4000)] + public void TestGetMaxPacketSize(int channel, int packageSize) + { + inner.GetMaxPacketSize(Arg.Any()).Returns(packageSize); + + Assert.That(middleware.GetMaxPacketSize(channel), Is.EqualTo(packageSize)); + + inner.Received(1).GetMaxPacketSize(Arg.Is(x => x == channel)); + inner.Received(0).GetMaxPacketSize(Arg.Is(x => x != channel)); + } + + [Test] + public void TestShutdown() + { + middleware.Shutdown(); + + inner.Received(1).Shutdown(); + } + + [Test] + [TestCase("localhost")] + [TestCase("example.com")] + public void TestClientConnect(string address) + { + middleware.ClientConnect(address); + + inner.Received(1).ClientConnect(address); + inner.Received(0).ClientConnect(Arg.Is(x => x != address)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestClientConnected(bool connected) + { + inner.ClientConnected().Returns(connected); + + Assert.That(middleware.ClientConnected(), Is.EqualTo(connected)); + + inner.Received(1).ClientConnected(); + } + + [Test] + public void TestClientDisconnect() + { + middleware.ClientDisconnect(); + + inner.Received(1).ClientDisconnect(); + } + + [Test] + [TestCase(Channels.Reliable)] + [TestCase(Channels.Unreliable)] + public void TestClientSend(int channel) + { + byte[] array = new byte[10]; + const int offset = 2; + const int count = 5; + ArraySegment segment = new ArraySegment(array, offset, count); + + middleware.ClientSend(segment, channel); + + inner.Received(1).ClientSend(Arg.Is>(x => x.Array == array && x.Offset == offset && x.Count == count), channel); + inner.Received(0).ClientSend(Arg.Any>(), Arg.Is(x => x != channel)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void TestServerActive(bool active) + { + inner.ServerActive().Returns(active); + + Assert.That(middleware.ServerActive(), Is.EqualTo(active)); + + inner.Received(1).ServerActive(); + } + + [Test] + public void TestServerStart() + { + middleware.ServerStart(); + + inner.Received(1).ServerStart(); + } + + [Test] + public void TestServerStop() + { + middleware.ServerStop(); + + inner.Received(1).ServerStop(); + } + + [Test] + [TestCase(0, 0)] + [TestCase(1, 0)] + [TestCase(0, 1)] + [TestCase(19, 1)] + public void TestServerSend(int id, int channel) + { + byte[] array = new byte[10]; + const int offset = 2; + const int count = 5; + ArraySegment segment = new ArraySegment(array, offset, count); + + middleware.ServerSend(id, segment, channel); + + inner.Received(1).ServerSend(id, Arg.Is>(x => x.Array == array && x.Offset == offset && x.Count == count), channel); + // only need to check first arg, + inner.Received(0).ServerSend(Arg.Is(x => x != id), Arg.Any>(), Arg.Any()); + } + + [Test] + [TestCase(0, "tcp4://localhost:7777")] + [TestCase(19, "tcp4://example.com:7777")] + public void TestServerGetClientAddress(int id, string result) + { + inner.ServerGetClientAddress(id).Returns(result); + + Assert.That(middleware.ServerGetClientAddress(id), Is.EqualTo(result)); + + inner.Received(1).ServerGetClientAddress(id); + inner.Received(0).ServerGetClientAddress(Arg.Is(x => x != id)); + + } + + [Test] + [TestCase("tcp4://localhost:7777")] + [TestCase("tcp4://example.com:7777")] + public void TestServerUri(string address) + { + Uri uri = new Uri(address); + inner.ServerUri().Returns(uri); + + Assert.That(middleware.ServerUri(), Is.EqualTo(uri)); + + inner.Received(1).ServerUri(); + } + + [Test] + public void TestClientConnectedCallback() + { + int called = 0; + middleware.OnClientConnected = () => + { + called++; + }; + // connect to give callback to inner + middleware.ClientConnect("localhost"); + + inner.OnClientConnected.Invoke(); + Assert.That(called, Is.EqualTo(1)); + + inner.OnClientConnected.Invoke(); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + public void TestClientDataReceivedCallback(int channel) + { + byte[] data = new byte[4]; + ArraySegment segment = new ArraySegment(data, 1, 2); + + int called = 0; + middleware.OnClientDataReceived = (d, c) => + { + called++; + Assert.That(c, Is.EqualTo(channel)); + Assert.That(d.Array, Is.EqualTo(segment.Array)); + Assert.That(d.Offset, Is.EqualTo(segment.Offset)); + Assert.That(d.Count, Is.EqualTo(segment.Count)); + }; + // connect to give callback to inner + middleware.ClientConnect("localhost"); + + inner.OnClientDataReceived.Invoke(segment, channel); + Assert.That(called, Is.EqualTo(1)); + + + data = new byte[4]; + segment = new ArraySegment(data, 0, 3); + + inner.OnClientDataReceived.Invoke(segment, channel); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + public void TestClientDisconnectedCallback() + { + int called = 0; + middleware.OnClientDisconnected = () => + { + called++; + }; + // connect to give callback to inner + middleware.ClientConnect("localhost"); + + inner.OnClientDisconnected.Invoke(); + Assert.That(called, Is.EqualTo(1)); + + inner.OnClientDisconnected.Invoke(); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + public void TestClientErrorCallback() + { + Exception exception = new InvalidDataException(); + + int called = 0; + middleware.OnClientError = (e) => + { + called++; + Assert.That(e, Is.EqualTo(exception)); + }; + // connect to give callback to inner + middleware.ClientConnect("localhost"); + + inner.OnClientError.Invoke(exception); + Assert.That(called, Is.EqualTo(1)); + + exception = new NullReferenceException(); + + inner.OnClientError.Invoke(exception); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(19)] + public void TestServerConnectedCallback(int id) + { + int called = 0; + middleware.OnServerConnected = (i) => + { + called++; + Assert.That(i, Is.EqualTo(id)); + }; + // start to give callback to inner + middleware.ServerStart(); + + inner.OnServerConnected.Invoke(id); + Assert.That(called, Is.EqualTo(1)); + + inner.OnServerConnected.Invoke(id); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + [TestCase(0, 0)] + [TestCase(1, 0)] + [TestCase(19, 0)] + [TestCase(0, 1)] + [TestCase(1, 1)] + [TestCase(19, 1)] + public void TestServerDataReceivedCallback(int id, int channel) + { + byte[] data = new byte[4]; + ArraySegment segment = new ArraySegment(data, 1, 2); + + int called = 0; + middleware.OnServerDataReceived = (i, d, c) => + { + called++; + Assert.That(i, Is.EqualTo(id)); + Assert.That(c, Is.EqualTo(channel)); + Assert.That(d.Array, Is.EqualTo(segment.Array)); + Assert.That(d.Offset, Is.EqualTo(segment.Offset)); + Assert.That(d.Count, Is.EqualTo(segment.Count)); + }; + // start to give callback to inner + middleware.ServerStart(); + + inner.OnServerDataReceived.Invoke(id, segment, channel); + Assert.That(called, Is.EqualTo(1)); + + + data = new byte[4]; + segment = new ArraySegment(data, 0, 3); + + inner.OnServerDataReceived.Invoke(id, segment, channel); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(19)] + public void TestServerDisconnectedCallback(int id) + { + int called = 0; + middleware.OnServerDisconnected = (i) => + { + called++; + Assert.That(i, Is.EqualTo(id)); + }; + // start to give callback to inner + middleware.ServerStart(); + + inner.OnServerDisconnected.Invoke(id); + Assert.That(called, Is.EqualTo(1)); + + inner.OnServerDisconnected.Invoke(id); + Assert.That(called, Is.EqualTo(2)); + } + + [Test] + [TestCase(0)] + [TestCase(1)] + [TestCase(19)] + public void TestServerErrorCallback(int id) + { + Exception exception = new InvalidDataException(); + + int called = 0; + middleware.OnServerError = (i, e) => + { + called++; + Assert.That(i, Is.EqualTo(id)); + Assert.That(e, Is.EqualTo(exception)); + }; + // start to give callback to inner + middleware.ServerStart(); + + inner.OnServerError.Invoke(id, exception); + Assert.That(called, Is.EqualTo(1)); + + exception = new NullReferenceException(); + + inner.OnServerError.Invoke(id, exception); + Assert.That(called, Is.EqualTo(2)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs.meta b/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs.meta new file mode 100644 index 000000000..9766d9cdd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MiddlewareTransportTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1ee4c7efa89013a41aeee942e60af4e7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef b/Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef new file mode 100644 index 000000000..60c6c9a96 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef @@ -0,0 +1,34 @@ +{ + "name": "Mirror.Tests", + "rootNamespace": "", + "references": [ + "Mirror", + "Mirror.Editor", + "Mirror.Components", + "Mirror.Tests.Common", + "Telepathy", + "UnityEngine.TestRunner", + "UnityEditor.TestRunner", + "Unity.Mirror.CodeGen", + "WeaverTestExtraAssembly" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "NSubstitute.dll", + "Castle.Core.dll", + "System.Threading.Tasks.Extensions.dll", + "Mono.CecilX.dll", + "nunit.framework.dll" + ], + "autoReferenced": false, + "defineConstraints": [ + "UNITY_INCLUDE_TESTS" + ], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef.meta b/Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef.meta new file mode 100644 index 000000000..4f2302303 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Mirror.Tests.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 8b489029f75e64a7bbf6918bf1a49e39 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/MultiplexTest.cs b/Assets/Mirror/Tests/Editor/MultiplexTest.cs new file mode 100644 index 000000000..795e7a1e0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MultiplexTest.cs @@ -0,0 +1,180 @@ +using System; +using NSubstitute; +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class MultiplexTest : MirrorTest + { + Transport transport1; + Transport transport2; + new MultiplexTransport transport; + + [SetUp] + public void Setup() + { + base.SetUp(); + + CreateGameObject(out _, out transport); + + transport1 = Substitute.For(); + transport2 = Substitute.For(); + transport.transports = new[] { transport1, transport2 }; + + transport.Awake(); + } + + // A Test behaves as an ordinary method + [Test] + public void TestAvailable() + { + transport1.Available().Returns(true); + transport2.Available().Returns(false); + Assert.That(transport.Available()); + } + + // A Test behaves as an ordinary method + [Test] + public void TestNotAvailable() + { + transport1.Available().Returns(false); + transport2.Available().Returns(false); + Assert.That(transport.Available(), Is.False); + } + + // A Test behaves as an ordinary method + [Test] + public void TestConnect() + { + transport1.Available().Returns(false); + transport2.Available().Returns(true); + transport.ClientConnect("some.server.com"); + + transport1.DidNotReceive().ClientConnect(Arg.Any()); + transport2.Received().ClientConnect("some.server.com"); + } + + // A Test behaves as an ordinary method + [Test] + public void TestConnectFirstUri() + { + Uri uri = new Uri("tcp://some.server.com"); + + transport1.Available().Returns(true); + transport2.Available().Returns(true); + + transport.ClientConnect(uri); + transport1.Received().ClientConnect(uri); + transport2.DidNotReceive().ClientConnect(uri); + } + + + // A Test behaves as an ordinary method + [Test] + public void TestConnectSecondUri() + { + Uri uri = new Uri("ws://some.server.com"); + + transport1.Available().Returns(true); + + // first transport does not support websocket + transport1 + .When(x => x.ClientConnect(uri)) + .Do(x => { throw new ArgumentException("Scheme not supported"); }); + + transport2.Available().Returns(true); + + transport.ClientConnect(uri); + transport2.Received().ClientConnect(uri); + } + + [Test] + public void TestConnected() + { + transport1.Available().Returns(true); + transport.ClientConnect("some.server.com"); + + transport1.ClientConnected().Returns(true); + + Assert.That(transport.ClientConnected()); + } + + [Test] + public void TestDisconnect() + { + transport1.Available().Returns(true); + transport.ClientConnect("some.server.com"); + + transport.ClientDisconnect(); + + transport1.Received().ClientDisconnect(); + } + + [Test] + public void TestClientSend() + { + transport1.Available().Returns(true); + transport.ClientConnect("some.server.com"); + + byte[] data = { 1, 2, 3 }; + ArraySegment segment = new ArraySegment(data); + + transport.ClientSend(segment, 3); + + transport1.Received().ClientSend(segment, 3); + } + + [Test] + public void TestClient1Connected() + { + transport1.Available().Returns(true); + transport2.Available().Returns(true); + + Action callback = Substitute.For(); + // find available + transport.Awake(); + // set event and connect to give event to inner + transport.OnClientConnected = callback; + transport.ClientConnect("localhost"); + transport1.OnClientConnected.Invoke(); + callback.Received().Invoke(); + } + + [Test] + public void TestClient2Connected() + { + transport1.Available().Returns(false); + transport2.Available().Returns(true); + + Action callback = Substitute.For(); + // find available + transport.Awake(); + // set event and connect to give event to inner + transport.OnClientConnected = callback; + transport.ClientConnect("localhost"); + transport2.OnClientConnected.Invoke(); + callback.Received().Invoke(); + } + + [Test] + public void TestServerConnected() + { + byte[] data = { 1, 2, 3 }; + ArraySegment segment = new ArraySegment(data); + + // on connect, send a message back + void SendMessage(int connectionId) + { + transport.ServerSend(connectionId, segment, 5); + } + + // set event and Start to give event to inner + transport.OnServerConnected = SendMessage; + transport.ServerStart(); + + transport1.OnServerConnected.Invoke(1); + + transport1.Received().ServerSend(1, segment, 5); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/MultiplexTest.cs.meta b/Assets/Mirror/Tests/Editor/MultiplexTest.cs.meta new file mode 100644 index 000000000..a4ec69160 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/MultiplexTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f0d8bef6afd464247bf433cdc5d3ff23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs new file mode 100644 index 000000000..9edc8087a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs @@ -0,0 +1,184 @@ +// dirty bits are powerful magic. +// add some tests to guarantee correct behaviour. +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + class NetworkBehaviourWithSyncVarsAndCollections : NetworkBehaviour + { + // SyncVars + [SyncVar] public int health; + [SyncVar] public int mana; + + // SyncCollections + public readonly SyncList list = new SyncList(); + public readonly SyncDictionary dict = new SyncDictionary(); + } + + public class NetworkBehaviourSyncVarDirtyBitsExposed : NetworkBehaviour + { + public ulong syncVarDirtyBitsExposed => syncVarDirtyBits; + } + + public class NetworkBehaviourDirtyBitsTests : MirrorEditModeTest + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // SyncLists are only set dirty while owner has observers. + // need a connection. + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + + [Test] + public void SetSyncVarDirtyBit() + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out NetworkBehaviourSyncVarDirtyBitsExposed comp); + + // set 3rd dirty bit. + comp.SetSyncVarDirtyBit(0b_00000000_00000100); + Assert.That(comp.syncVarDirtyBitsExposed, Is.EqualTo(0b_00000000_00000100)); + + // set 5th dirty bit. + // both 3rd and 5th should be set. + comp.SetSyncVarDirtyBit(0b_00000000_00010000); + Assert.That(comp.syncVarDirtyBitsExposed, Is.EqualTo(0b_00000000_00010100)); + } + + // changing a SyncObject (collection) should modify the dirty mask. + [Test] + public void SyncObjectsSetDirtyBits() + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out NetworkBehaviourWithSyncVarsAndCollections comp); + + // not dirty by default + Assert.That(comp.syncObjectDirtyBits, Is.EqualTo(0UL)); + + // change the list. should be dirty now. + comp.list.Add(42); + Assert.That(comp.syncObjectDirtyBits, Is.EqualTo(0b01)); + + // change the dict. should both be dirty. + comp.dict[42] = null; + Assert.That(comp.syncObjectDirtyBits, Is.EqualTo(0b11)); + } + + [Test] + public void IsDirty() + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity identity, out NetworkBehaviourWithSyncVarsAndCollections comp); + + // not dirty by default + Assert.That(comp.IsDirty(), Is.False); + + // changing a [SyncVar] should set it dirty + ++comp.health; + Assert.That(comp.IsDirty(), Is.True); + comp.ClearAllDirtyBits(); + + // changing a SyncCollection should set it dirty + comp.list.Add(42); + Assert.That(comp.IsDirty(), Is.True); + comp.ClearAllDirtyBits(); + + // it should only be dirty after syncInterval elapsed + comp.syncInterval = float.MaxValue; + Assert.That(comp.IsDirty(), Is.False); + } + + [Test] + public void ClearAllDirtyBitsClearsSyncVarDirtyBits() + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out EmptyBehaviour emptyBehaviour); + + // set syncinterval so dirtybit works fine + emptyBehaviour.syncInterval = 0; + Assert.That(emptyBehaviour.IsDirty(), Is.False); + + // set one syncvar dirty bit + emptyBehaviour.SetSyncVarDirtyBit(1); + Assert.That(emptyBehaviour.IsDirty(), Is.True); + + // clear it + emptyBehaviour.ClearAllDirtyBits(); + Assert.That(emptyBehaviour.IsDirty(), Is.False); + } + + [Test] + public void ClearAllDirtyBitsClearsSyncObjectsDirtyBits() + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out NetworkBehaviourWithSyncVarsAndCollections comp); + + // set syncinterval so dirtybit works fine + comp.syncInterval = 0; + Assert.That(comp.IsDirty(), Is.False); + + // dirty the synclist + comp.list.Add(42); + Assert.That(comp.IsDirty, Is.True); + + // clear bits should clear synclist bits too + comp.ClearAllDirtyBits(); + Assert.That(comp.IsDirty, Is.False); + } + + // NetworkServer.Broadcast clears all dirty bits in all spawned + // identity's components if they have no observers. + // + // this way dirty bit tracking only starts after first observer. + // otherwise first observer would still get dirty update for everything + // that was dirty before he observed. even though he already got the + // full state in spawn packet. + [Test] + public void DirtyBitsAreClearedForSpawnedWithoutObservers() + { + // need one player, one monster + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity player, NetworkServer.localConnection); + CreateNetworkedAndSpawn(out _, out NetworkIdentity monster, out NetworkBehaviourWithSyncVarsAndCollections monsterComp); + + // without AOI, player connection sees everyone automatically. + // remove the monster from observing. + // remvoe player from monster observers. + monster.RemoveObserver(player.connectionToClient); + Assert.That(monster.observers.Count, Is.EqualTo(0)); + + // modify something in the monster so that dirty bit is set + monsterComp.syncInterval = 0; + ++monsterComp.health; + Assert.That(monsterComp.IsDirty(), Is.True); + + // add first observer. dirty bits should be cleared. + monster.AddObserver(player.connectionToClient); + Assert.That(monsterComp.IsDirty(), Is.False); + } + } + + // hook tests can only be ran when inheriting from NetworkBehaviour + public class NetworkBehaviourDirtyBitsHookGuardTester : NetworkBehaviour + { + [Test] + public void HookGuard() + { + // set hook guard for some bits + for (int i = 0; i < 10; ++i) + { + ulong bit = 1ul << i; + + // should be false by default + Assert.That(GetSyncVarHookGuard(bit), Is.False); + + // set true + SetSyncVarHookGuard(bit, true); + Assert.That(GetSyncVarHookGuard(bit), Is.True); + + // set false again + SetSyncVarHookGuard(bit, false); + Assert.That(GetSyncVarHookGuard(bit), Is.False); + } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs.meta new file mode 100644 index 000000000..c9e0879cf --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourDirtyBitsTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 71cd60f7177a4d3da75bb87f5ac13a18 +timeCreated: 1631772886 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs new file mode 100644 index 000000000..99dbbd3e9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs @@ -0,0 +1,341 @@ +using NUnit.Framework; +using UnityEngine; + +// Note: Weaver doesn't run on nested class so use namespace to group classes instead +namespace Mirror.Tests.NetworkBehaviourSerialize +{ + #region No OnSerialize/OnDeserialize override + abstract class AbstractBehaviour : NetworkBehaviour + { + public readonly SyncList syncListInAbstract = new SyncList(); + + [SyncVar] + public int SyncFieldInAbstract; + } + + class BehaviourWithSyncVar : NetworkBehaviour + { + public readonly SyncList syncList = new SyncList(); + + [SyncVar] + public int SyncField; + } + + class OverrideBehaviourFromSyncVar : AbstractBehaviour {} + + class OverrideBehaviourWithSyncVarFromSyncVar : AbstractBehaviour + { + public readonly SyncList syncListInOverride = new SyncList(); + + [SyncVar] + public int SyncFieldInOverride; + } + + class MiddleClass : AbstractBehaviour + { + // class with no sync var + } + + class SubClass : MiddleClass + { + // class with sync var + // this is to make sure that override works correctly if base class doesn't have sync vars + [SyncVar] + public Vector3 anotherSyncField; + } + + class MiddleClassWithSyncVar : AbstractBehaviour + { + // class with sync var + [SyncVar] + public string syncFieldInMiddle; + } + + class SubClassFromSyncVar : MiddleClassWithSyncVar + { + // class with sync var + // this is to make sure that override works correctly if base class doesn't have sync vars + [SyncVar] + public Vector3 syncFieldInSub; + } + #endregion + + #region OnSerialize/OnDeserialize override + + class BehaviourWithSyncVarWithOnSerialize : NetworkBehaviour + { + public readonly SyncList syncList = new SyncList(); + + [SyncVar] + public int SyncField; + + public float customSerializeField; + + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + writer.WriteFloat(customSerializeField); + return base.OnSerialize(writer, initialState); + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + customSerializeField = reader.ReadFloat(); + base.OnDeserialize(reader, initialState); + } + } + + class OverrideBehaviourFromSyncVarWithOnSerialize : AbstractBehaviour + { + public float customSerializeField; + + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + writer.WriteFloat(customSerializeField); + return base.OnSerialize(writer, initialState); + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + customSerializeField = reader.ReadFloat(); + base.OnDeserialize(reader, initialState); + } + } + + class OverrideBehaviourWithSyncVarFromSyncVarWithOnSerialize : AbstractBehaviour + { + public readonly SyncList syncListInOverride = new SyncList(); + + [SyncVar] + public int SyncFieldInOverride; + + public float customSerializeField; + + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + writer.WriteFloat(customSerializeField); + return base.OnSerialize(writer, initialState); + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + customSerializeField = reader.ReadFloat(); + base.OnDeserialize(reader, initialState); + } + } + #endregion + + public class NetworkBehaviourSerializeTest : MirrorEditModeTest + { + static void SyncNetworkBehaviour(NetworkBehaviour source, NetworkBehaviour target, bool initialState) + { + using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + { + source.OnSerialize(writer, initialState); + + using (PooledNetworkReader reader = NetworkReaderPool.GetReader(writer.ToArraySegment())) + { + target.OnDeserialize(reader, initialState); + } + } + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // SyncLists are only set dirty while owner has observers. + // need a connection. + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void BehaviourWithSyncVarTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out BehaviourWithSyncVar source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out BehaviourWithSyncVar target); + + source.SyncField = 10; + source.syncList.Add(true); + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncField, Is.EqualTo(10)); + Assert.That(target.syncList.Count, Is.EqualTo(1)); + Assert.That(target.syncList[0], Is.True); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourFromSyncVarTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourFromSyncVar source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourFromSyncVar target); + + source.SyncFieldInAbstract = 12; + source.syncListInAbstract.Add(true); + source.syncListInAbstract.Add(false); + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(12)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(2)); + Assert.That(target.syncListInAbstract[0], Is.True); + Assert.That(target.syncListInAbstract[1], Is.False); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourWithSyncVarFromSyncVarTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourWithSyncVarFromSyncVar source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourWithSyncVarFromSyncVar target); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.SyncFieldInOverride = 52; + source.syncListInOverride.Add(false); + source.syncListInOverride.Add(true); + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + + Assert.That(target.SyncFieldInOverride, Is.EqualTo(52)); + Assert.That(target.syncListInOverride.Count, Is.EqualTo(2)); + Assert.That(target.syncListInOverride[0], Is.False); + Assert.That(target.syncListInOverride[1], Is.True); + } + + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SubClassTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out SubClass source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out SubClass target); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.anotherSyncField = new Vector3(40, 20, 10); + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + Assert.That(target.anotherSyncField, Is.EqualTo(new Vector3(40, 20, 10))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SubClassFromSyncVarTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out SubClassFromSyncVar source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out SubClassFromSyncVar target); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.syncFieldInMiddle = "Hello World!"; + source.syncFieldInSub = new Vector3(40, 20, 10); + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + Assert.That(target.syncFieldInMiddle, Is.EqualTo("Hello World!")); + Assert.That(target.syncFieldInSub, Is.EqualTo(new Vector3(40, 20, 10))); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void BehaviourWithSyncVarWithOnSerializeTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out BehaviourWithSyncVarWithOnSerialize source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out BehaviourWithSyncVarWithOnSerialize target); + + source.SyncField = 10; + source.syncList.Add(true); + + source.customSerializeField = 20.5f; + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncField, Is.EqualTo(10)); + Assert.That(target.syncList.Count, Is.EqualTo(1)); + Assert.That(target.syncList[0], Is.True); + + Assert.That(target.customSerializeField, Is.EqualTo(20.5f)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourFromSyncVarWithOnSerializeTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourFromSyncVarWithOnSerialize source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourFromSyncVarWithOnSerialize target); + + source.SyncFieldInAbstract = 12; + source.syncListInAbstract.Add(true); + source.syncListInAbstract.Add(false); + + source.customSerializeField = 20.5f; + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(12)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(2)); + Assert.That(target.syncListInAbstract[0], Is.True); + Assert.That(target.syncListInAbstract[1], Is.False); + + Assert.That(target.customSerializeField, Is.EqualTo(20.5f)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void OverrideBehaviourWithSyncVarFromSyncVarWithOnSerializeTest(bool initialState) + { + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourWithSyncVarFromSyncVarWithOnSerialize source); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out OverrideBehaviourWithSyncVarFromSyncVarWithOnSerialize target); + + source.SyncFieldInAbstract = 10; + source.syncListInAbstract.Add(true); + + source.SyncFieldInOverride = 52; + source.syncListInOverride.Add(false); + source.syncListInOverride.Add(true); + + source.customSerializeField = 20.5f; + + SyncNetworkBehaviour(source, target, initialState); + + Assert.That(target.SyncFieldInAbstract, Is.EqualTo(10)); + Assert.That(target.syncListInAbstract.Count, Is.EqualTo(1)); + Assert.That(target.syncListInAbstract[0], Is.True); + + + Assert.That(target.SyncFieldInOverride, Is.EqualTo(52)); + Assert.That(target.syncListInOverride.Count, Is.EqualTo(2)); + Assert.That(target.syncListInOverride[0], Is.False); + Assert.That(target.syncListInOverride[1], Is.True); + + Assert.That(target.customSerializeField, Is.EqualTo(20.5f)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs.meta new file mode 100644 index 000000000..b76e74fed --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourSerializeTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 253d419ce2900134482c3c8b76883c60 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs b/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs new file mode 100644 index 000000000..343f0c828 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs @@ -0,0 +1,884 @@ +using Mirror.RemoteCalls; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests +{ + class EmptyBehaviour : NetworkBehaviour {} + + // we need to inherit from networkbehaviour to test protected functions + public class NetworkBehaviourDelegateComponent : NetworkBehaviour + { + public static void Delegate(NetworkBehaviour comp, NetworkReader reader, NetworkConnection senderConnection) {} + public static void Delegate2(NetworkBehaviour comp, NetworkReader reader, NetworkConnection senderConnection) {} + } + + // we need to inherit from networkbehaviour to test protected functions + public class NetworkBehaviourSetSyncVarGameObjectComponent : NetworkBehaviour + { + //[SyncVar] + public GameObject test; + // usually generated by weaver + public uint testNetId; + + // SetSyncVarGameObject wrapper to expose it + public void SetSyncVarGameObjectExposed(GameObject newGameObject, ulong dirtyBit) => + SetSyncVarGameObject(newGameObject, ref test, dirtyBit, ref testNetId); + } + + // we need to inherit from networkbehaviour to test protected functions + public class NetworkBehaviourGetSyncVarGameObjectComponent : NetworkBehaviour + { + //[SyncVar] + public GameObject test; + // usually generated by weaver + public uint testNetId; + + // SetSyncVarGameObject wrapper to expose it + public GameObject GetSyncVarGameObjectExposed() => + GetSyncVarGameObject(testNetId, ref test); + } + + // we need to inherit from networkbehaviour to test protected functions + public class NetworkBehaviourSetSyncVarNetworkIdentityComponent : NetworkBehaviour + { + //[SyncVar] + public NetworkIdentity test; + // usually generated by weaver + public uint testNetId; + + // SetSyncVarNetworkIdentity wrapper to expose it + public void SetSyncVarNetworkIdentityExposed(NetworkIdentity newNetworkIdentity, ulong dirtyBit) => + SetSyncVarNetworkIdentity(newNetworkIdentity, ref test, dirtyBit, ref testNetId); + } + + // we need to inherit from networkbehaviour to test protected functions + public class NetworkBehaviourGetSyncVarNetworkIdentityComponent : NetworkBehaviour + { + //[SyncVar] + public NetworkIdentity test; + // usually generated by weaver + public uint testNetId; + + // SetSyncVarNetworkIdentity wrapper to expose it + public NetworkIdentity GetSyncVarNetworkIdentityExposed() => + GetSyncVarNetworkIdentity(testNetId, ref test); + } + + // we need to inherit from networkbehaviour to test protected functions + public class OnStopClientComponent : NetworkBehaviour + { + public int called; + public override void OnStopClient() => ++called; + } + + // we need to inherit from networkbehaviour to test protected functions + public class OnStartClientComponent : NetworkBehaviour + { + public int called; + public override void OnStartClient() => ++called; + } + + // we need to inherit from networkbehaviour to test protected functions + public class OnStartLocalPlayerComponent : NetworkBehaviour + { + public int called; + public override void OnStartLocalPlayer() => ++called; + } + + public class NetworkBehaviourTests : MirrorEditModeTest + { + [TearDown] + public override void TearDown() + { + NetworkServer.RemoveLocalConnection(); + base.TearDown(); + } + + [Test] + public void IsServerOnly() + { + CreateNetworked(out _, out NetworkIdentity identity, out EmptyBehaviour emptyBehaviour); + + // call OnStartServer so isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // isServerOnly should be true when isServer = true && isClient = false + Assert.That(emptyBehaviour.isServer, Is.True); + Assert.That(emptyBehaviour.isClient, Is.False); + Assert.That(emptyBehaviour.isServerOnly, Is.True); + } + + [Test] + public void IsClientOnly() + { + CreateNetworked(out _, out NetworkIdentity identity, out EmptyBehaviour emptyBehaviour); + + // isClientOnly should be true when isServer = false && isClient = true + identity.isClient = true; + Assert.That(emptyBehaviour.isServer, Is.False); + Assert.That(emptyBehaviour.isClient, Is.True); + Assert.That(emptyBehaviour.isClientOnly, Is.True); + } + + [Test] + public void HasNoAuthorityByDefault() + { + // no authority by default + CreateNetworked(out _, out _, out EmptyBehaviour emptyBehaviour); + Assert.That(emptyBehaviour.hasAuthority, Is.False); + } + + [Test] + public void HasIdentitysNetId() + { + CreateNetworked(out _, out NetworkIdentity identity, out EmptyBehaviour emptyBehaviour); + identity.netId = 42; + Assert.That(emptyBehaviour.netId, Is.EqualTo(42)); + } + + [Test] + public void HasIdentitysConnectionToServer() + { + CreateNetworked(out _, out NetworkIdentity identity, out EmptyBehaviour emptyBehaviour); + identity.connectionToServer = new LocalConnectionToServer(); + Assert.That(emptyBehaviour.connectionToServer, Is.EqualTo(identity.connectionToServer)); + } + + [Test] + public void HasIdentitysConnectionToClient() + { + CreateNetworked(out _, out NetworkIdentity identity, out EmptyBehaviour emptyBehaviour); + identity.connectionToClient = new LocalConnectionToClient(); + Assert.That(emptyBehaviour.connectionToClient, Is.EqualTo(identity.connectionToClient)); + } + + [Test] + public void ComponentIndex() + { + // create a NetworkIdentity with two components + CreateNetworked(out GameObject _, out NetworkIdentity _, out EmptyBehaviour first, out EmptyBehaviour second); + Assert.That(first.ComponentIndex, Is.EqualTo(0)); + Assert.That(second.ComponentIndex, Is.EqualTo(1)); + } + + [Test, Ignore("NetworkServerTest.SendCommand does it already")] + public void SendCommandInternal() {} + + [Test, Ignore("ClientRpcTest.cs tests Rpcs already")] + public void SendRPCInternal() {} + + [Test, Ignore("TargetRpcTest.cs tests TargetRpcs already")] + public void SendTargetRPCInternal() {} + + [Test] + public void RegisterDelegateDoesntOverwrite() + { + // registerdelegate is protected, but we can use + // RegisterCommandDelegate which calls RegisterDelegate + int registeredHash1 = RemoteProcedureCalls.RegisterDelegate( + typeof(NetworkBehaviourDelegateComponent), + nameof(NetworkBehaviourDelegateComponent.Delegate), + RemoteCallType.Command, + NetworkBehaviourDelegateComponent.Delegate, + false); + + // registering the exact same one should be fine. it should simply + // do nothing. + int registeredHash2 = RemoteProcedureCalls.RegisterDelegate( + typeof(NetworkBehaviourDelegateComponent), + nameof(NetworkBehaviourDelegateComponent.Delegate), + RemoteCallType.Command, + NetworkBehaviourDelegateComponent.Delegate, + false); + + // registering the same name with a different callback shouldn't + // work + LogAssert.Expect(LogType.Error, $"Function {typeof(NetworkBehaviourDelegateComponent)}.{nameof(NetworkBehaviourDelegateComponent.Delegate)} and {typeof(NetworkBehaviourDelegateComponent)}.{nameof(NetworkBehaviourDelegateComponent.Delegate2)} have the same hash. Please rename one of them"); + int registeredHash3 = RemoteProcedureCalls.RegisterDelegate( + typeof(NetworkBehaviourDelegateComponent), + nameof(NetworkBehaviourDelegateComponent.Delegate), + RemoteCallType.Command, + NetworkBehaviourDelegateComponent.Delegate2, + false); + + // clean up + RemoteProcedureCalls.RemoveDelegate(registeredHash1); + RemoteProcedureCalls.RemoveDelegate(registeredHash2); + RemoteProcedureCalls.RemoveDelegate(registeredHash3); + } + + [Test] + public void GetDelegate() + { + // registerdelegate is protected, but we can use + // RegisterCommandDelegate which calls RegisterDelegate + int registeredHash = RemoteProcedureCalls.RegisterDelegate( + typeof(NetworkBehaviourDelegateComponent), + nameof(NetworkBehaviourDelegateComponent.Delegate), + RemoteCallType.Command, + NetworkBehaviourDelegateComponent.Delegate, + false); + + // get handler + int cmdHash = nameof(NetworkBehaviourDelegateComponent.Delegate).GetStableHashCode(); + RemoteCallDelegate func = RemoteProcedureCalls.GetDelegate(cmdHash); + RemoteCallDelegate expected = NetworkBehaviourDelegateComponent.Delegate; + Assert.That(func, Is.EqualTo(expected)); + + // invalid hash should return null handler + RemoteCallDelegate funcNull = RemoteProcedureCalls.GetDelegate(1234); + Assert.That(funcNull, Is.Null); + + // clean up + RemoteProcedureCalls.RemoveDelegate(registeredHash); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualZeroNetIdNullIsTrue() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + // null and identity.netid==0 returns true (=equal) + // + // later we should reevaluate if this is so smart or not. might be + // better to return false here. + // => we possibly return false so that resync doesn't happen when + // GO disappears? or not? + bool result = NetworkBehaviour.SyncVarGameObjectEqual(null, identity.netId); + Assert.That(result, Is.True); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualNull() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + // our identity should have a netid for comparing + identity.netId = 42; + + // null should return false + bool result = NetworkBehaviour.SyncVarGameObjectEqual(null, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualZeroNetIdAndGOWithoutIdentityComponentIsTrue() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + CreateNetworked(out GameObject go, out NetworkIdentity _); + + // null and identity.netid==0 returns true (=equal) + // + // later we should reevaluate if this is so smart or not. might be + // better to return false here. + // => we possibly return false so that resync doesn't happen when + // GO disappears? or not? + bool result = NetworkBehaviour.SyncVarGameObjectEqual(go, identity.netId); + Assert.That(result, Is.True); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualWithoutIdentityComponent() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject without networkidentity component should return false + CreateNetworked(out GameObject go, out NetworkIdentity _); + bool result = NetworkBehaviour.SyncVarGameObjectEqual(go, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualValidGOWithDifferentNetId() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject with valid networkidentity and netid that is different + CreateNetworked(out GameObject go, out NetworkIdentity ni); + ni.netId = 43; + bool result = NetworkBehaviour.SyncVarGameObjectEqual(go, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualValidGOWithSameNetId() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject with valid networkidentity and netid that is different + CreateNetworked(out GameObject go, out NetworkIdentity ni); + ni.netId = 42; + bool result = NetworkBehaviour.SyncVarGameObjectEqual(go, identity.netId); + Assert.That(result, Is.True); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualUnspawnedGO() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject with valid networkidentity and 0 netid that is unspawned + CreateNetworked(out GameObject go, out NetworkIdentity ni); + LogAssert.Expect(LogType.Warning, $"SetSyncVarGameObject GameObject {go} has a zero netId. Maybe it is not spawned yet?"); + bool result = NetworkBehaviour.SyncVarGameObjectEqual(go, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarGameObjectEqual should be static later + [Test] + public void SyncVarGameObjectEqualUnspawnedGOZeroNetIdIsTrue() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // unspawned go and identity.netid==0 returns true (=equal) + CreateNetworked(out GameObject go, out NetworkIdentity ni); + LogAssert.Expect(LogType.Warning, $"SetSyncVarGameObject GameObject {go} has a zero netId. Maybe it is not spawned yet?"); + bool result = NetworkBehaviour.SyncVarGameObjectEqual(go, identity.netId); + Assert.That(result, Is.True); + } + + // NOTE: SyncVarNetworkIdentityEqual should be static later + [Test] + public void SyncVarNetworkIdentityEqualZeroNetIdNullIsTrue() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // null and identity.netid==0 returns true (=equal) + // + // later we should reevaluate if this is so smart or not. might be + // better to return false here. + // => we possibly return false so that resync doesn't happen when + // GO disappears? or not? + bool result = NetworkBehaviour.SyncVarNetworkIdentityEqual(null, identity.netId); + Assert.That(result, Is.True); + } + + // NOTE: SyncVarNetworkIdentityEqual should be static later + [Test] + public void SyncVarNetworkIdentityEqualNull() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // null should return false + bool result = NetworkBehaviour.SyncVarNetworkIdentityEqual(null, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarNetworkIdentityEqual should be static later + [Test] + public void SyncVarNetworkIdentityEqualValidIdentityWithDifferentNetId() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject with valid networkidentity and netid that is different + CreateNetworked(out GameObject go, out NetworkIdentity ni); + ni.netId = 43; + bool result = NetworkBehaviour.SyncVarNetworkIdentityEqual(ni, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarNetworkIdentityEqual should be static later + [Test] + public void SyncVarNetworkIdentityEqualValidIdentityWithSameNetId() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject with valid networkidentity and netid that is different + CreateNetworked(out GameObject _, out NetworkIdentity ni); + ni.netId = 42; + bool result = NetworkBehaviour.SyncVarNetworkIdentityEqual(ni, identity.netId); + Assert.That(result, Is.True); + } + + // NOTE: SyncVarNetworkIdentityEqual should be static later + [Test] + public void SyncVarNetworkIdentityEqualUnspawnedIdentity() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // our identity should have a netid for comparing + identity.netId = 42; + + // gameobject with valid networkidentity and 0 netid that is unspawned + CreateNetworked(out GameObject go, out NetworkIdentity ni); + LogAssert.Expect(LogType.Warning, $"SetSyncVarNetworkIdentity NetworkIdentity {ni} has a zero netId. Maybe it is not spawned yet?"); + bool result = NetworkBehaviour.SyncVarNetworkIdentityEqual(ni, identity.netId); + Assert.That(result, Is.False); + } + + // NOTE: SyncVarNetworkIdentityEqual should be static later + [Test] + public void SyncVarNetworkIdentityEqualUnspawnedIdentityZeroNetIdIsTrue() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // unspawned go and identity.netid==0 returns true (=equal) + CreateNetworked(out GameObject go, out NetworkIdentity ni); + LogAssert.Expect(LogType.Warning, $"SetSyncVarNetworkIdentity NetworkIdentity {ni} has a zero netId. Maybe it is not spawned yet?"); + bool result = NetworkBehaviour.SyncVarNetworkIdentityEqual(ni, identity.netId); + Assert.That(result, Is.True); + } + + [Test] + public void SetSyncVarGameObjectWithValidObject() + { + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourSetSyncVarGameObjectComponent comp); + + // create a valid GameObject with networkidentity and netid + CreateNetworked(out GameObject go, out NetworkIdentity ni); + ni.netId = 43; + + // set the GameObject SyncVar + Assert.That(comp.IsDirty(), Is.False); + comp.SetSyncVarGameObjectExposed(go, 1ul); + Assert.That(comp.test, Is.EqualTo(go)); + Assert.That(comp.testNetId, Is.EqualTo(ni.netId)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void SetSyncVarGameObjectNull() + { + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourSetSyncVarGameObjectComponent comp); + + // set some existing GO+netId first to check if it is going to be + // overwritten + CreateGameObject(out GameObject go); + comp.test = go; + comp.testNetId = 43; + + // set the GameObject SyncVar to null + Assert.That(comp.IsDirty(), Is.False); + comp.SetSyncVarGameObjectExposed(null, 1ul); + Assert.That(comp.test, Is.EqualTo(null)); + Assert.That(comp.testNetId, Is.EqualTo(0)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void SetSyncVarGameObjectWithoutNetworkIdentity() + { + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourSetSyncVarGameObjectComponent comp); + + // set some existing GO+netId first to check if it is going to be + // overwritten + CreateGameObject(out GameObject go); + comp.test = go; + comp.testNetId = 43; + + // create test GO with no networkidentity + CreateGameObject(out GameObject test); + + // set the GameObject SyncVar to 'test' GO without netidentity. + // -> the way it currently works is that it sets netId to 0, but + // it DOES set gameObjectField to the GameObject without the + // NetworkIdentity, instead of setting it to null because it's + // seemingly invalid. + // -> there might be a deeper reason why UNET did that. perhaps for + // cases where we assign a GameObject before the network was + // fully started and has no netId or networkidentity yet etc. + // => it works, so let's keep it for now + Assert.That(comp.IsDirty(), Is.False); + comp.SetSyncVarGameObjectExposed(test, 1ul); + Assert.That(comp.test, Is.EqualTo(test)); + Assert.That(comp.testNetId, Is.EqualTo(0)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void SetSyncVarGameObjectZeroNetId() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity, out NetworkBehaviourSetSyncVarGameObjectComponent comp); + + // set some existing GO+netId first to check if it is going to be + // overwritten + CreateGameObject(out GameObject go); + comp.test = go; + comp.testNetId = 43; + + // create test GO with networkidentity and zero netid + CreateNetworked(out GameObject test, out NetworkIdentity ni); + Assert.That(ni.netId, Is.EqualTo(0)); + + // set the GameObject SyncVar to 'test' GO with zero netId. + // -> the way it currently works is that it sets netId to 0, but + // it DOES set gameObjectField to the GameObject without the + // NetworkIdentity, instead of setting it to null because it's + // seemingly invalid. + // -> there might be a deeper reason why UNET did that. perhaps for + // cases where we assign a GameObject before the network was + // fully started and has no netId or networkidentity yet etc. + // => it works, so let's keep it for now + Assert.That(comp.IsDirty(), Is.False); + LogAssert.Expect(LogType.Warning, $"SetSyncVarGameObject GameObject {test} has a zero netId. Maybe it is not spawned yet?"); + comp.SetSyncVarGameObjectExposed(test, 1ul); + Assert.That(comp.test, Is.EqualTo(test)); + Assert.That(comp.testNetId, Is.EqualTo(0)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void GetSyncVarGameObjectOnServer() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarGameObjectComponent comp); + + // call OnStartServer so isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // create a syncable GameObject + CreateNetworked(out GameObject go, out NetworkIdentity ni); + ni.netId = identity.netId + 1; + + // assign it in the component + comp.test = go; + comp.testNetId = ni.netId; + + // get it on the server. should simply return the field instead of + // doing any netId lookup like on the client + GameObject result = comp.GetSyncVarGameObjectExposed(); + Assert.That(result, Is.EqualTo(go)); + + // clean up: set isServer false first, otherwise Destroy instead of DestroyImmediate is called + identity.netId = 0; + } + + [Test] + public void GetSyncVarGameObjectOnServerNull() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarGameObjectComponent comp); + + // call OnStartServer and assign netId so isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // get it on the server. null should work fine. + GameObject result = comp.GetSyncVarGameObjectExposed(); + Assert.That(result, Is.Null); + } + + [Test] + public void GetSyncVarGameObjectOnClient() + { + // start server & connect client because we need spawn functions + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out _); + + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // are we on client and not on server? + identity.isClient = true; + Assert.That(identity.isServer, Is.False); + + // create a networked object with test component + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourGetSyncVarGameObjectComponent comp); + + // create a spawned, syncable GameObject + // (client tries to look up via netid, so needs to be spawned) + CreateNetworkedAndSpawn( + out GameObject serverGO, out NetworkIdentity serverIdentity, + out GameObject clientGO, out NetworkIdentity clientIdentity); + + // assign ONLY netId in the component. assume that GameObject was + // assigned earlier but client walked so far out of range that it + // was despawned on the client. so it's forced to do the netId look- + // up. + Assert.That(comp.test, Is.Null); + comp.testNetId = clientIdentity.netId; + + // get it on the client. should look up netId in spawned + GameObject result = comp.GetSyncVarGameObjectExposed(); + Assert.That(result, Is.EqualTo(clientGO)); + } + + [Test] + public void GetSyncVarGameObjectOnClientNull() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // are we on client and not on server? + identity.isClient = true; + Assert.That(identity.isServer, Is.False); + + // create a networked object with test component + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourGetSyncVarGameObjectComponent comp); + + // get it on the client. null should be supported. + GameObject result = comp.GetSyncVarGameObjectExposed(); + Assert.That(result, Is.Null); + } + + [Test] + public void SetSyncVarNetworkIdentityWithValidObject() + { + // create a networked object with test component + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourSetSyncVarNetworkIdentityComponent comp); + + // create a valid GameObject with networkidentity and netid + CreateNetworked(out GameObject _, out NetworkIdentity ni); + ni.netId = 43; + + // set the NetworkIdentity SyncVar + Assert.That(comp.IsDirty(), Is.False); + comp.SetSyncVarNetworkIdentityExposed(ni, 1ul); + Assert.That(comp.test, Is.EqualTo(ni)); + Assert.That(comp.testNetId, Is.EqualTo(ni.netId)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void SetSyncVarNetworkIdentityNull() + { + // create a networked object with test component + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourSetSyncVarNetworkIdentityComponent comp); + + // set some existing NI+netId first to check if it is going to be + // overwritten + CreateNetworked(out GameObject _, out NetworkIdentity ni); + comp.test = ni; + comp.testNetId = 43; + + // set the NetworkIdentity SyncVar to null + Assert.That(comp.IsDirty(), Is.False); + comp.SetSyncVarNetworkIdentityExposed(null, 1ul); + Assert.That(comp.test, Is.EqualTo(null)); + Assert.That(comp.testNetId, Is.EqualTo(0)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void SetSyncVarNetworkIdentityZeroNetId() + { + CreateNetworked(out _, out _, out NetworkBehaviourSetSyncVarNetworkIdentityComponent comp); + + // set some existing NI+netId first to check if it is going to be + // overwritten + CreateNetworked(out GameObject _, out NetworkIdentity ni); + comp.test = ni; + comp.testNetId = 43; + + // create test GO with networkidentity and zero netid + CreateNetworked(out GameObject _, out NetworkIdentity testNi); + Assert.That(testNi.netId, Is.EqualTo(0)); + + // set the NetworkIdentity SyncVar to 'test' GO with zero netId. + // -> the way it currently works is that it sets netId to 0, but + // it DOES set gameObjectField to the GameObject without the + // NetworkIdentity, instead of setting it to null because it's + // seemingly invalid. + // -> there might be a deeper reason why UNET did that. perhaps for + // cases where we assign a GameObject before the network was + // fully started and has no netId or networkidentity yet etc. + // => it works, so let's keep it for now + Assert.That(comp.IsDirty(), Is.False); + LogAssert.Expect(LogType.Warning, $"SetSyncVarNetworkIdentity NetworkIdentity {testNi} has a zero netId. Maybe it is not spawned yet?"); + comp.SetSyncVarNetworkIdentityExposed(testNi, 1ul); + Assert.That(comp.test, Is.EqualTo(testNi)); + Assert.That(comp.testNetId, Is.EqualTo(0)); + Assert.That(comp.IsDirty(), Is.True); + } + + [Test] + public void GetSyncVarNetworkIdentityOnServer() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarNetworkIdentityComponent comp); + + // call OnStartServer so isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // create a syncable GameObject + CreateNetworked(out _, out NetworkIdentity ni); + ni.netId = identity.netId + 1; + + // assign it in the component + comp.test = ni; + comp.testNetId = ni.netId; + + // get it on the server. should simply return the field instead of + // doing any netId lookup like on the client + NetworkIdentity result = comp.GetSyncVarNetworkIdentityExposed(); + Assert.That(result, Is.EqualTo(ni)); + } + + [Test] + public void GetSyncVarNetworkIdentityOnServerNull() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarNetworkIdentityComponent comp); + + // call OnStartServer so isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // get it on the server. null should work fine. + NetworkIdentity result = comp.GetSyncVarNetworkIdentityExposed(); + Assert.That(result, Is.Null); + } + + [Test] + public void GetSyncVarNetworkIdentityOnClient() + { + // start server & connect client because we need spawn functions + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out _); + + CreateNetworked(out GameObject _, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarNetworkIdentityComponent comp); + + // are we on client and not on server? + identity.isClient = true; + Assert.That(identity.isServer, Is.False); + + // create a spawned, syncable GameObject + // (client tries to look up via netid, so needs to be spawned) + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverIdentity, + out _, out NetworkIdentity clientIdentity); + + // assign ONLY netId in the component. assume that GameObject was + // assigned earlier but client walked so far out of range that it + // was despawned on the client. so it's forced to do the netId look- + // up. + Assert.That(comp.test, Is.Null); + comp.testNetId = clientIdentity.netId; + + // get it on the client. should look up netId in spawned + NetworkIdentity result = comp.GetSyncVarNetworkIdentityExposed(); + Assert.That(result, Is.EqualTo(clientIdentity)); + } + + [Test] + public void GetSyncVarNetworkIdentityOnClientNull() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out NetworkBehaviourGetSyncVarNetworkIdentityComponent comp); + + // are we on client and not on server? + identity.isClient = true; + Assert.That(identity.isServer, Is.False); + + // get it on the client. null should be supported. + NetworkIdentity result = comp.GetSyncVarNetworkIdentityExposed(); + Assert.That(result, Is.Null); + } + + [Test] + public void SerializeAndDeserializeObjectsAll() + { + CreateNetworked(out GameObject _, out NetworkIdentity _, out NetworkBehaviourWithSyncVarsAndCollections comp); + + // add values to synclist + comp.list.Add(42); + comp.list.Add(43); + + // serialize it + NetworkWriter writer = new NetworkWriter(); + comp.SerializeObjectsAll(writer); + + // clear original list + comp.list.Clear(); + Assert.That(comp.list.Count, Is.EqualTo(0)); + + // deserialize it + NetworkReader reader = new NetworkReader(writer.ToArray()); + comp.DeSerializeObjectsAll(reader); + Assert.That(comp.list.Count, Is.EqualTo(2)); + Assert.That(comp.list[0], Is.EqualTo(42)); + Assert.That(comp.list[1], Is.EqualTo(43)); + } + + [Test] + public void SerializeAndDeserializeObjectsDelta() + { + // SyncLists are only set dirty while owner has observers. + // need a connection. + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out NetworkBehaviourWithSyncVarsAndCollections comp); + + // add to synclist + comp.list.Add(42); + comp.list.Add(43); + + // serialize it + NetworkWriter writer = new NetworkWriter(); + comp.SerializeObjectsDelta(writer); + + // clear original list + comp.list.Clear(); + Assert.That(comp.list.Count, Is.EqualTo(0)); + + // deserialize it + NetworkReader reader = new NetworkReader(writer.ToArray()); + comp.DeSerializeObjectsDelta(reader); + Assert.That(comp.list.Count, Is.EqualTo(2)); + Assert.That(comp.list[0], Is.EqualTo(42)); + Assert.That(comp.list[1], Is.EqualTo(43)); + } + + [Test] + public void OnStopClient() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out OnStopClientComponent comp); + identity.OnStopClient(); + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStartClient() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out OnStartClientComponent comp); + identity.OnStartClient(); + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStartLocalPlayer() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out OnStartLocalPlayerComponent comp); + identity.OnStartLocalPlayer(); + Assert.That(comp.called, Is.EqualTo(1)); + } + } + + // we need to inherit from networkbehaviour to test protected functions + public class NetworkBehaviourInitSyncObjectTester : NetworkBehaviour + { + [Test] + public void InitSyncObject() + { + SyncObject syncObject = new SyncList(); + InitSyncObject(syncObject); + Assert.That(syncObjects.Count, Is.EqualTo(1)); + Assert.That(syncObjects[0], Is.EqualTo(syncObject)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs.meta new file mode 100644 index 000000000..49167f61a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkBehaviourTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0e4164d273d6e4d71bd39cc246f454b2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkClientTests.cs b/Assets/Mirror/Tests/Editor/NetworkClientTests.cs new file mode 100644 index 000000000..729d23256 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkClientTests.cs @@ -0,0 +1,128 @@ +using System; +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class NetworkClientTests : MirrorEditModeTest + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + // we need a server to connect to + NetworkServer.Listen(10); + } + + [Test] + public void ServerIp() + { + NetworkClient.ConnectHost(); + Assert.That(NetworkClient.serverIp, Is.EqualTo("localhost")); + } + + [Test] + public void IsConnected() + { + Assert.That(NetworkClient.isConnected, Is.False); + NetworkClient.ConnectHost(); + Assert.That(NetworkClient.isConnected, Is.True); + } + + [Test] + public void ConnectUri() + { + NetworkClient.Connect(new Uri("memory://localhost")); + // update transport so connect event is processed + UpdateTransport(); + Assert.That(NetworkClient.isConnected, Is.True); + } + + [Test] + public void DisconnectInHostMode() + { + NetworkClient.ConnectHost(); + Assert.That(NetworkClient.isConnected, Is.True); + Assert.That(NetworkServer.localConnection, !Is.Null); + + NetworkClient.Disconnect(); + Assert.That(NetworkClient.isConnected, Is.False); + Assert.That(NetworkServer.localConnection, Is.Null); + } + + [Test, Ignore("NetworkServerTest.SendClientToServerMessage does it already")] + public void Send() {} + + // test to guarantee Disconnect() eventually calls OnClientDisconnected. + // prevents https://github.com/vis2k/Mirror/issues/2818 forever. + // previously there was a bug where: + // - Disconnect() sets state = Disconnected + // - Transport processes it + // - OnTransportDisconnected() early returns because + // state == Disconnected already, so it wouldn't call the event. + [Test] + public void DisconnectCallsOnClientDisconnected_Remote() + { + // setup hook + bool called = false; + NetworkClient.OnDisconnectedEvent = () => called = true; + + // connect + ConnectClientBlocking(out _); + + // disconnect & process everything + NetworkClient.Disconnect(); + UpdateTransport(); + + // was it called? + Assert.That(called, Is.True); + } + + // same as above, but for host mode + // prevents https://github.com/vis2k/Mirror/issues/2818 forever. + [Test] + public void DisconnectCallsOnClientDisconnected_HostMode() + { + // setup hook + bool called = false; + NetworkClient.OnDisconnectedEvent = () => called = true; + + // connect host + NetworkClient.ConnectHost(); + + // disconnect & process everything + NetworkClient.Disconnect(); + UpdateTransport(); + + // was it called? + Assert.That(called, Is.True); + } + + [Test] + public void ShutdownCleanup() + { + // add some test event hooks to make sure they are cleaned up. + // there used to be a bug where they wouldn't be cleaned up. + NetworkClient.OnConnectedEvent = () => {}; + NetworkClient.OnDisconnectedEvent = () => {}; + + NetworkClient.Shutdown(); + + Assert.That(NetworkClient.handlers.Count, Is.EqualTo(0)); + Assert.That(NetworkClient.spawned.Count, Is.EqualTo(0)); + Assert.That(NetworkClient.spawnableObjects.Count, Is.EqualTo(0)); + + Assert.That(NetworkClient.connectState, Is.EqualTo(ConnectState.None)); + + Assert.That(NetworkClient.connection, Is.Null); + Assert.That(NetworkClient.localPlayer, Is.Null); + + Assert.That(NetworkClient.ready, Is.False); + Assert.That(NetworkClient.isSpawnFinished, Is.False); + Assert.That(NetworkClient.isLoadingScene, Is.False); + + Assert.That(NetworkClient.OnConnectedEvent, Is.Null); + Assert.That(NetworkClient.OnDisconnectedEvent, Is.Null); + Assert.That(NetworkClient.OnErrorEvent, Is.Null); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkClientTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkClientTests.cs.meta new file mode 100644 index 000000000..548cf375e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkClientTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e55c00322b97542828f7d27c8182c60a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs b/Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs new file mode 100644 index 000000000..fe150e468 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs @@ -0,0 +1,90 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class NetworkConnectionToClientTests : MirrorEditModeTest + { + List clientReceived = new List(); + + [SetUp] + public override void SetUp() + { + base.SetUp(); + transport.OnClientDataReceived = (message, channelId) => { + byte[] array = new byte[message.Count]; + Buffer.BlockCopy(message.Array, message.Offset, array, 0, message.Count); + clientReceived.Add(array); + }; + transport.ServerStart(); + transport.ClientConnect("localhost"); + Assert.That(transport.ServerActive, Is.True); + Assert.That(transport.ClientConnected, Is.True); + } + + [TearDown] + public override void TearDown() + { + clientReceived.Clear(); + base.TearDown(); + } + + [Test] + public void Send_BatchesUntilUpdate() + { + // create connection and send + NetworkConnectionToClient connection = new NetworkConnectionToClient(42); + byte[] message = {0x01, 0x02}; + connection.Send(new ArraySegment(message)); + + // Send() should only add to batch, not send anything yet + UpdateTransport(); + Assert.That(clientReceived.Count, Is.EqualTo(0)); + + // updating the connection should now send + connection.Update(); + UpdateTransport(); + Assert.That(clientReceived.Count, Is.EqualTo(1)); + } + + // IMPORTANT + // + // there was a bug where batching resets .Position instead of .Length, + // resulting in extremely high bandwidth where if the last message's + // Length was 2, and the current message's Length was 1, then we would + // still send a writer with Length = 2 because we did not reset .Length! + // -> let's try to send a big message, update, then send a small message + [Test] + public void SendBatchingResetsPreviousWriter() + { + // batching adds 8 byte timestamp header + const int BatchHeader = 8; + + // create connection + NetworkConnectionToClient connection = new NetworkConnectionToClient(42); + + // send and update big message + byte[] message = {0x01, 0x02}; + connection.Send(new ArraySegment(message)); + connection.Update(); + UpdateTransport(); + Assert.That(clientReceived.Count, Is.EqualTo(1)); + Assert.That(clientReceived[0].Length, Is.EqualTo(BatchHeader + 2)); + Assert.That(clientReceived[0][BatchHeader + 0], Is.EqualTo(0x01)); + Assert.That(clientReceived[0][BatchHeader + 1], Is.EqualTo(0x02)); + + // clear previous + clientReceived.Clear(); + + // send a smaller message + message = new byte[]{0xFF}; + connection.Send(new ArraySegment(message)); + connection.Update(); + UpdateTransport(); + Assert.That(clientReceived.Count, Is.EqualTo(1)); + Assert.That(clientReceived[0].Length, Is.EqualTo(BatchHeader + 1)); + Assert.That(clientReceived[0][BatchHeader + 0], Is.EqualTo(0xFF)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs.meta new file mode 100644 index 000000000..94948fcd6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkConnectionToClientTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 22aee0cf91224ec9925f7faabc073b09 +timeCreated: 1611722538 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs b/Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs new file mode 100644 index 000000000..ecc1a4460 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs @@ -0,0 +1,180 @@ +// OnDe/SerializeSafely tests. +using System; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests +{ + public class NetworkIdentitySerializationTests : MirrorEditModeTest + { + // writers are always needed. create in setup for convenience. + NetworkWriter ownerWriter; + NetworkWriter observersWriter; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + ownerWriter = new NetworkWriter(); + observersWriter = new NetworkWriter(); + } + + // serialize -> deserialize. multiple components to be sure. + // one for Owner, one for Observer + [Test] + public void OnSerializeAndDeserializeAllSafely() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out SerializeTest1NetworkBehaviour comp1, + out SerializeTest2NetworkBehaviour comp2); + + // set some unique values to serialize + comp1.value = 12345; + comp1.syncMode = SyncMode.Observers; + comp2.value = "67890"; + comp2.syncMode = SyncMode.Owner; + + // serialize all + identity.OnSerializeAllSafely(true, ownerWriter, observersWriter); + + // owner & observers should have written something + Assert.That(ownerWriter.Position, Is.GreaterThan(0)); + Assert.That(observersWriter.Position, Is.GreaterThan(0)); + Debug.Log($"ownerWriter: {BitConverter.ToString(ownerWriter.ToArray())}"); + Debug.Log($"observersWriter: {BitConverter.ToString(observersWriter.ToArray())}"); + + // reset component values + comp1.value = 0; + comp2.value = null; + + // deserialize all for owner + NetworkReader reader = new NetworkReader(ownerWriter.ToArray()); + identity.OnDeserializeAllSafely(reader, true); + Assert.That(comp1.value, Is.EqualTo(12345)); + Assert.That(comp2.value, Is.EqualTo("67890")); + + // reset component values + comp1.value = 0; + comp2.value = null; + + // deserialize all for observers + reader = new NetworkReader(observersWriter.ToArray()); + identity.OnDeserializeAllSafely(reader, true); + // observers mode, should be in data + Assert.That(comp1.value, Is.EqualTo(12345)); + // owner mode, should not be in data + Assert.That(comp2.value, Is.EqualTo(null)); + } + + // serialization should work even if a component throws an exception. + // so if first component throws, second should still be serialized fine. + [Test] + public void SerializationException() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out SerializeExceptionNetworkBehaviour compExc, + out SerializeTest2NetworkBehaviour comp2); + + // set some unique values to serialize + compExc.syncMode = SyncMode.Observers; + comp2.value = "67890"; + comp2.syncMode = SyncMode.Owner; + + // serialize all - should work even if compExc throws an exception + // error log because of the exception is expected + LogAssert.ignoreFailingMessages = true; + identity.OnSerializeAllSafely(true, ownerWriter, observersWriter); + LogAssert.ignoreFailingMessages = false; + + // owner & observers should have written something + Assert.That(ownerWriter.Position, Is.GreaterThan(0)); + Assert.That(observersWriter.Position, Is.GreaterThan(0)); + + // reset component values + comp2.value = null; + + // deserialize all for owner - should work even if compExc throws an exception + NetworkReader reader = new NetworkReader(ownerWriter.ToArray()); + // error log because of the exception is expected + LogAssert.ignoreFailingMessages = true; + identity.OnDeserializeAllSafely(reader, true); + LogAssert.ignoreFailingMessages = false; + Assert.That(comp2.value, Is.EqualTo("67890")); + + // reset component values + comp2.value = null; + + // deserialize all for observers - should work even if compExc throws an exception + reader = new NetworkReader(observersWriter.ToArray()); + // error log because of the exception is expected + LogAssert.ignoreFailingMessages = true; + identity.OnDeserializeAllSafely(reader, true); + LogAssert.ignoreFailingMessages = false; + // owner mode, should not be in data + Assert.That(comp2.value, Is.EqualTo(null)); + } + + // OnSerializeAllSafely supports at max 64 components, because our + // dirty mask is ulong and can only handle so many bits. + [Test] + public void TooManyComponents() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity); + + // add 65 components + for (int i = 0; i < 65; ++i) + gameObject.AddComponent(); + + // CreateNetworked already initializes the components. + // let's reset and initialize again with the added ones. + identity.Reset(); + identity.Awake(); + + // ignore error from creating cache (has its own test) + LogAssert.ignoreFailingMessages = true; + _ = identity.NetworkBehaviours; + LogAssert.ignoreFailingMessages = false; + + // try to serialize + identity.OnSerializeAllSafely(true, ownerWriter, observersWriter); + + // Should still write with too many Components because NetworkBehavioursCache should handle the error + Assert.That(ownerWriter.Position, Is.GreaterThan(0)); + Assert.That(observersWriter.Position, Is.GreaterThan(0)); + } + + // OnDeserializeSafely should be able to detect and handle serialization + // mismatches (= if compA writes 10 bytes but only reads 8 or 12, it + // shouldn't break compB's serialization. otherwise we end up with + // insane runtime errors like monsters that look like npcs. that's what + // happened back in the day with UNET). + [Test] + public void SerializationMismatch() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out SerializeMismatchNetworkBehaviour compMiss, + out SerializeTest2NetworkBehaviour comp); + + // set some unique values to serialize + comp.value = "67890"; + + // serialize + identity.OnSerializeAllSafely(true, ownerWriter, observersWriter); + + // reset component values + comp.value = null; + + // deserialize all + NetworkReader reader = new NetworkReader(ownerWriter.ToArray()); + // warning log because of serialization mismatch + LogAssert.ignoreFailingMessages = true; + identity.OnDeserializeAllSafely(reader, true); + LogAssert.ignoreFailingMessages = false; + + // the mismatch component will fail, but the one before and after + // should still work fine. that's the whole point. + Assert.That(comp.value, Is.EqualTo("67890")); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs.meta new file mode 100644 index 000000000..4ab5b3b19 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkIdentitySerializationTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 0ac8a004d23d4b91bbfd910183bdb89d +timeCreated: 1628417849 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs new file mode 100644 index 000000000..d0b8dba43 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs @@ -0,0 +1,967 @@ +using System; +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests +{ + class StartServerNetworkBehaviour : NetworkBehaviour + { + internal bool onStartServerInvoked; + public override void OnStartServer() => onStartServerInvoked = true; + } + + class StartServerExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStartServer() + { + ++called; + throw new Exception("some exception"); + } + } + + class StartClientExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStartClient() + { + ++called; + throw new Exception("some exception"); + } + } + + class StartAuthorityExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStartAuthority() + { + ++called; + throw new Exception("some exception"); + } + } + + class StartAuthorityCalledNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStartAuthority() => ++called; + } + + class StopAuthorityExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStopAuthority() + { + ++called; + throw new Exception("some exception"); + } + } + + class StopAuthorityCalledNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStopAuthority() => ++called; + } + + class StartLocalPlayerExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStartLocalPlayer() + { + ++called; + throw new Exception("some exception"); + } + } + + class StartLocalPlayerCalledNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStartLocalPlayer() => ++called; + } + + class NetworkDestroyExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStopClient() + { + ++called; + throw new Exception("some exception"); + } + } + + class NetworkDestroyCalledNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStopClient() => ++called; + } + + class StopServerCalledNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStopServer() => ++called; + } + + class StopServerExceptionNetworkBehaviour : NetworkBehaviour + { + public int called; + public override void OnStopServer() + { + ++called; + throw new Exception("some exception"); + } + } + + class SerializeTest1NetworkBehaviour : NetworkBehaviour + { + public int value; + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + writer.WriteInt(value); + return true; + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + value = reader.ReadInt(); + } + } + + class SerializeTest2NetworkBehaviour : NetworkBehaviour + { + public string value; + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + writer.WriteString(value); + return true; + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + value = reader.ReadString(); + } + } + + class SerializeExceptionNetworkBehaviour : NetworkBehaviour + { + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + throw new Exception("some exception"); + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + throw new Exception("some exception"); + } + } + + class SerializeMismatchNetworkBehaviour : NetworkBehaviour + { + public int value; + public override bool OnSerialize(NetworkWriter writer, bool initialState) + { + writer.WriteInt(value); + // one too many + writer.WriteInt(value); + return true; + } + public override void OnDeserialize(NetworkReader reader, bool initialState) + { + value = reader.ReadInt(); + } + } + + class IsClientServerCheckComponent : NetworkBehaviour + { + // OnStartClient + internal bool OnStartClient_isClient; + internal bool OnStartClient_isServer; + internal bool OnStartClient_isLocalPlayer; + public override void OnStartClient() + { + OnStartClient_isClient = isClient; + OnStartClient_isServer = isServer; + OnStartClient_isLocalPlayer = isLocalPlayer; + } + + // OnStartServer + internal bool OnStartServer_isClient; + internal bool OnStartServer_isServer; + internal bool OnStartServer_isLocalPlayer; + public override void OnStartServer() + { + OnStartServer_isClient = isClient; + OnStartServer_isServer = isServer; + OnStartServer_isLocalPlayer = isLocalPlayer; + } + + // OnStartLocalPlayer + internal bool OnStartLocalPlayer_isClient; + internal bool OnStartLocalPlayer_isServer; + internal bool OnStartLocalPlayer_isLocalPlayer; + public override void OnStartLocalPlayer() + { + OnStartLocalPlayer_isClient = isClient; + OnStartLocalPlayer_isServer = isServer; + OnStartLocalPlayer_isLocalPlayer = isLocalPlayer; + } + + // Start + internal bool Start_isClient; + internal bool Start_isServer; + internal bool Start_isLocalPlayer; + public void Start() + { + Start_isClient = isClient; + Start_isServer = isServer; + Start_isLocalPlayer = isLocalPlayer; + } + + // OnDestroy + internal bool OnDestroy_isClient; + internal bool OnDestroy_isServer; + internal bool OnDestroy_isLocalPlayer; + public void OnDestroy() + { + OnDestroy_isClient = isClient; + OnDestroy_isServer = isServer; + OnDestroy_isLocalPlayer = isLocalPlayer; + } + } + + public class NetworkIdentityTests : MirrorEditModeTest + { + [Test] + public void OnStartServerTest() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StartServerNetworkBehaviour component1, out StartServerNetworkBehaviour component2); + identity.OnStartServer(); + + Assert.That(component1.onStartServerInvoked); + Assert.That(component2.onStartServerInvoked); + } + + // check isClient/isServer/isLocalPlayer in server-only mode + [Test] + public void ServerMode_IsFlags_Test() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity _, out IsClientServerCheckComponent component); + + // start the server + NetworkServer.Listen(1000); + + // spawn it + NetworkServer.Spawn(gameObject); + + // OnStartServer should have been called. check the flags. + Assert.That(component.OnStartServer_isClient, Is.EqualTo(false)); + Assert.That(component.OnStartServer_isLocalPlayer, Is.EqualTo(false)); + Assert.That(component.OnStartServer_isServer, Is.EqualTo(true)); + } + + // check isClient/isServer/isLocalPlayer in host mode + [Test] + public void HostMode_IsFlags_Test() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity, out IsClientServerCheckComponent component); + + // start the server + NetworkServer.Listen(1000); + + // start the client + NetworkClient.ConnectHost(); + + // set is as local player + NetworkClient.InternalAddPlayer(identity); + + // spawn it + NetworkServer.Spawn(gameObject); + + // OnStartServer should have been called. check the flags. + Assert.That(component.OnStartServer_isClient, Is.EqualTo(true)); + Assert.That(component.OnStartServer_isLocalPlayer, Is.EqualTo(true)); + Assert.That(component.OnStartServer_isServer, Is.EqualTo(true)); + + // stop the client + NetworkServer.RemoveLocalConnection(); + } + + [Test] + public void GetSetAssetId() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // assign a guid + Guid guid = new Guid(0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B); + identity.assetId = guid; + + // did it work? + Assert.That(identity.assetId, Is.EqualTo(guid)); + } + + [Test] + public void SetAssetId_GivesErrorIfOneExists() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + if (identity.assetId == Guid.Empty) + { + identity.assetId = Guid.NewGuid(); + } + + Guid guid1 = identity.assetId; + + // assign a guid + Guid guid2 = Guid.NewGuid(); + LogAssert.Expect(LogType.Error, $"Can not Set AssetId on NetworkIdentity '{identity.name}' because it already had an assetId, current assetId '{guid1:N}', attempted new assetId '{guid2:N}'"); + identity.assetId = guid2; + + // guid was changed + Assert.That(identity.assetId, Is.EqualTo(guid1)); + } + + [Test] + public void SetAssetId_GivesErrorForEmptyGuid() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + if (identity.assetId == Guid.Empty) + { + identity.assetId = Guid.NewGuid(); + } + + Guid guid1 = identity.assetId; + + // assign a guid + Guid guid2 = new Guid(); + LogAssert.Expect(LogType.Error, $"Can not set AssetId to empty guid on NetworkIdentity '{identity.name}', old assetId '{guid1:N}'"); + identity.assetId = guid2; + + // guid was NOT changed + Assert.That(identity.assetId, Is.EqualTo(guid1)); + } + + [Test] + public void SetAssetId_DoesNotGiveErrorIfBothOldAndNewAreEmpty() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + Debug.Assert(identity.assetId == Guid.Empty, "assetId needs to be empty at the start of this test"); + // assign a guid + Guid guid2 = new Guid(); + // expect no errors + identity.assetId = guid2; + + // guid was still empty + Assert.That(identity.assetId, Is.EqualTo(Guid.Empty)); + } + + [Test] + public void SetClientOwner() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // SetClientOwner + LocalConnectionToClient original = new LocalConnectionToClient(); + identity.SetClientOwner(original); + Assert.That(identity.connectionToClient, Is.EqualTo(original)); + + // setting it when it's already set shouldn't overwrite the original + LocalConnectionToClient overwrite = new LocalConnectionToClient(); + // will log a warning + LogAssert.ignoreFailingMessages = true; + identity.SetClientOwner(overwrite); + Assert.That(identity.connectionToClient, Is.EqualTo(original)); + LogAssert.ignoreFailingMessages = false; + } + + [Test] + public void RemoveObserver() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // call OnStartServer so that observers dict is created + identity.OnStartServer(); + + // add an observer connection + NetworkConnectionToClient connection = new NetworkConnectionToClient(42); + identity.observers[connection.connectionId] = connection; + + // RemoveObserver with invalid connection should do nothing + identity.RemoveObserver(new NetworkConnectionToClient(43)); + Assert.That(identity.observers.Count, Is.EqualTo(1)); + + // RemoveObserver with existing connection should remove it + identity.RemoveObserver(connection); + Assert.That(identity.observers.Count, Is.EqualTo(0)); + } + + [Test] + public void AssignSceneID() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // Awake will have assigned a random sceneId of format 0x00000000FFFFFFFF + // -> make sure that one was assigned, and that the left part was + // left empty for scene hash + Assert.That(identity.sceneId, !Is.Zero); + Assert.That(identity.sceneId & 0xFFFFFFFF00000000, Is.EqualTo(0x0000000000000000)); + + // make sure that Awake added it to sceneIds dict + Assert.That(NetworkIdentity.GetSceneIdentity(identity.sceneId), !Is.Null); + } + + [Test] + public void SetSceneIdSceneHashPartInternal() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // Awake will have assigned a random sceneId of format 0x00000000FFFFFFFF + // -> make sure that one was assigned, and that the left part was + // left empty for scene hash + Assert.That(identity.sceneId, !Is.Zero); + Assert.That(identity.sceneId & 0xFFFFFFFF00000000, Is.EqualTo(0x0000000000000000)); + ulong rightPart = identity.sceneId; + + // set scene hash + identity.SetSceneIdSceneHashPartInternal(); + + // make sure that the right part is still the random sceneid + Assert.That(identity.sceneId & 0x00000000FFFFFFFF, Is.EqualTo(rightPart)); + + // make sure that the left part is a scene hash now + Assert.That(identity.sceneId & 0xFFFFFFFF00000000, !Is.Zero); + ulong finished = identity.sceneId; + + // calling it again should said the exact same hash again + identity.SetSceneIdSceneHashPartInternal(); + Assert.That(identity.sceneId, Is.EqualTo(finished)); + } + + [Test] + public void OnValidateSetupIDsSetsEmptyAssetIDForSceneObject() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // OnValidate will have been called. make sure that assetId was set + // to 0 empty and not anything else, because this is a scene object + Assert.That(identity.assetId, Is.EqualTo(Guid.Empty)); + } + + [Test] + public void OnStartServerCallsComponentsAndCatchesExceptions() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StartServerExceptionNetworkBehaviour comp); + + // make sure that comp.OnStartServer was called and make sure that + // the exception was caught and not thrown in here. + // an exception in OnStartServer should be caught, so that one + // component's exception doesn't stop all other components from + // being initialized + // (an error log is expected though) + LogAssert.ignoreFailingMessages = true; + // should catch the exception internally and not throw it + identity.OnStartServer(); + Assert.That(comp.called, Is.EqualTo(1)); + LogAssert.ignoreFailingMessages = false; + } + + [Test] + public void OnStartClientCallsComponentsAndCatchesExceptions() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StartClientExceptionNetworkBehaviour comp); + + // make sure that comp.OnStartClient was called and make sure that + // the exception was caught and not thrown in here. + // an exception in OnStartClient should be caught, so that one + // component's exception doesn't stop all other components from + // being initialized + // (an error log is expected though) + LogAssert.ignoreFailingMessages = true; + // should catch the exception internally and not throw it + identity.OnStartClient(); + Assert.That(comp.called, Is.EqualTo(1)); + LogAssert.ignoreFailingMessages = false; + + // we have checks to make sure that it's only called once. + // let's see if they work. + identity.OnStartClient(); + // same as before? + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStartAuthorityCallsComponentsAndCatchesExceptions() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StartAuthorityExceptionNetworkBehaviour comp); + + // make sure that comp.OnStartAuthority was called and make sure that + // the exception was caught and not thrown in here. + // an exception in OnStartAuthority should be caught, so that one + // component's exception doesn't stop all other components from + // being initialized + // (an error log is expected though) + LogAssert.ignoreFailingMessages = true; + // should catch the exception internally and not throw it + identity.OnStartAuthority(); + Assert.That(comp.called, Is.EqualTo(1)); + LogAssert.ignoreFailingMessages = false; + } + + [Test] + public void OnStopAuthorityCallsComponentsAndCatchesExceptions() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StopAuthorityExceptionNetworkBehaviour comp); + + // make sure that comp.OnStopAuthority was called and make sure that + // the exception was caught and not thrown in here. + // an exception in OnStopAuthority should be caught, so that one + // component's exception doesn't stop all other components from + // being initialized + // (an error log is expected though) + LogAssert.ignoreFailingMessages = true; + // should catch the exception internally and not throw it + identity.OnStopAuthority(); + Assert.That(comp.called, Is.EqualTo(1)); + LogAssert.ignoreFailingMessages = false; + } + + [Test] + public void AssignAndRemoveClientAuthority() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // test the callback too + int callbackCalled = 0; + NetworkConnection callbackConnection = null; + NetworkIdentity callbackIdentity = null; + bool callbackState = false; + NetworkIdentity.clientAuthorityCallback += (conn, networkIdentity, state) => + { + ++callbackCalled; + callbackConnection = conn; + callbackIdentity = identity; + callbackState = state; + }; + + // create connections + CreateLocalConnectionPair(out LocalConnectionToClient owner, out LocalConnectionToServer clientConnection); + owner.isReady = true; + + // setup NetworkServer/Client connections so messages are handled + NetworkClient.connection = clientConnection; + NetworkServer.connections[owner.connectionId] = owner; + + // add client handlers + int spawnCalled = 0; + void Handler(SpawnMessage _) => ++spawnCalled; + NetworkClient.RegisterHandler(Handler, false); + + // assigning authority should only work on server. + // if isServer is false because server isn't running yet then it + // should fail. + // error log is expected + LogAssert.ignoreFailingMessages = true; + bool result = identity.AssignClientAuthority(owner); + LogAssert.ignoreFailingMessages = false; + Assert.That(result, Is.False); + + // server is needed + NetworkServer.Listen(1); + + // call OnStartServer so that isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // assign authority + result = identity.AssignClientAuthority(owner); + Assert.That(result, Is.True); + Assert.That(identity.connectionToClient, Is.EqualTo(owner)); + Assert.That(callbackCalled, Is.EqualTo(1)); + Assert.That(callbackConnection, Is.EqualTo(owner)); + Assert.That(callbackIdentity, Is.EqualTo(identity)); + Assert.That(callbackState, Is.EqualTo(true)); + + // shouldn't be able to assign authority while already owned by + // another connection + // error log is expected + LogAssert.ignoreFailingMessages = true; + result = identity.AssignClientAuthority(new NetworkConnectionToClient(43)); + LogAssert.ignoreFailingMessages = false; + Assert.That(result, Is.False); + Assert.That(identity.connectionToClient, Is.EqualTo(owner)); + Assert.That(callbackCalled, Is.EqualTo(1)); + + // someone might try to remove authority by assigning null. + // make sure this fails. + // error log is expected + LogAssert.ignoreFailingMessages = true; + result = identity.AssignClientAuthority(null); + LogAssert.ignoreFailingMessages = false; + Assert.That(result, Is.False); + + // removing authority while not isServer shouldn't work. + // only allow it on server. + identity.isServer = false; + + // error log is expected + LogAssert.ignoreFailingMessages = true; + identity.RemoveClientAuthority(); + LogAssert.ignoreFailingMessages = false; + Assert.That(identity.connectionToClient, Is.EqualTo(owner)); + Assert.That(callbackCalled, Is.EqualTo(1)); + + // enable isServer again + identity.isServer = true; + + // removing authority for the main player object shouldn't work + // set connection's player object + owner.identity = identity; + // error log is expected + LogAssert.ignoreFailingMessages = true; + identity.RemoveClientAuthority(); + LogAssert.ignoreFailingMessages = false; + Assert.That(identity.connectionToClient, Is.EqualTo(owner)); + Assert.That(callbackCalled, Is.EqualTo(1)); + + // removing authority for a non-main-player object should work + owner.identity = null; + identity.RemoveClientAuthority(); + Assert.That(identity.connectionToClient, Is.Null); + Assert.That(callbackCalled, Is.EqualTo(2)); + // the one that was removed + Assert.That(callbackConnection, Is.EqualTo(owner)); + Assert.That(callbackIdentity, Is.EqualTo(identity)); + Assert.That(callbackState, Is.EqualTo(false)); + } + + [Test] + public void NotifyAuthorityCallsOnStartStopAuthority() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StartAuthorityCalledNetworkBehaviour compStart, out StopAuthorityCalledNetworkBehaviour compStop); + + // set authority from false to true, which should call OnStartAuthority + identity.hasAuthority = true; + identity.NotifyAuthority(); + // shouldn't be touched + Assert.That(identity.hasAuthority, Is.True); + // start should be called + Assert.That(compStart.called, Is.EqualTo(1)); + // stop shouldn't + Assert.That(compStop.called, Is.EqualTo(0)); + + // set it to true again, should do nothing because already true + identity.hasAuthority = true; + identity.NotifyAuthority(); + // shouldn't be touched + Assert.That(identity.hasAuthority, Is.True); + // same as before + Assert.That(compStart.called, Is.EqualTo(1)); + // same as before + Assert.That(compStop.called, Is.EqualTo(0)); + + // set it to false, should call OnStopAuthority + identity.hasAuthority = false; + identity.NotifyAuthority(); + // should be changed + Assert.That(identity.hasAuthority, Is.False); + // same as before + Assert.That(compStart.called, Is.EqualTo(1)); + // stop should be called + Assert.That(compStop.called, Is.EqualTo(1)); + + // set it to false again, should do nothing because already false + identity.hasAuthority = false; + identity.NotifyAuthority(); + // shouldn't be touched + Assert.That(identity.hasAuthority, Is.False); + // same as before + Assert.That(compStart.called, Is.EqualTo(1)); + // same as before + Assert.That(compStop.called, Is.EqualTo(1)); + } + + // OnStartServer in host mode should set isClient=true + [Test] + public void OnStartServerInHostModeSetsIsClientTrue() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // call client connect so that internals are set up + // (it won't actually successfully connect) + NetworkClient.Connect("localhost"); + + // manually invoke transport.OnConnected so that NetworkClient.active is set to true + Transport.activeTransport.OnClientConnected.Invoke(); + Assert.That(NetworkClient.active, Is.True); + + // isClient needs to be true in OnStartServer if in host mode. + // this is a test for a bug that we fixed, where isClient was false + // in OnStartServer if in host mode because in host mode, we only + // connect the client after starting the server, hence isClient would + // be false in OnStartServer until way later. + // -> we have the workaround in OnStartServer, so let's also test to + // make sure that nobody ever breaks it again + Assert.That(identity.isClient, Is.False); + identity.OnStartServer(); + Assert.That(identity.isClient, Is.True); + } + + [Test] + public void CreatingNetworkBehavioursCacheShouldLogErrorForTooComponents() + { + CreateNetworked(out GameObject gameObject, out NetworkIdentity identity); + + // add byte.MaxValue+1 components + for (int i = 0; i < byte.MaxValue + 1; ++i) + { + gameObject.AddComponent(); + } + + // CreateNetworked already initializes the components. + // let's reset and initialize again with the added ones. + identity.Reset(); + identity.Awake(); + + // call NetworkBehaviours property to create the cache + LogAssert.Expect(LogType.Error, new Regex($"Only {byte.MaxValue} NetworkBehaviour components are allowed for NetworkIdentity.+")); + _ = identity.NetworkBehaviours; + } + + [Test] + public void OnStartLocalPlayer() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out StartLocalPlayerExceptionNetworkBehaviour compEx, + out StartLocalPlayerCalledNetworkBehaviour comp); + + // make sure our test values are set to 0 + Assert.That(compEx.called, Is.EqualTo(0)); + Assert.That(comp.called, Is.EqualTo(0)); + + // call OnStartLocalPlayer in identity + // one component will throw an exception, but that shouldn't stop + // OnStartLocalPlayer from being called in the second one + // exception will log an error + LogAssert.ignoreFailingMessages = true; + identity.OnStartLocalPlayer(); + LogAssert.ignoreFailingMessages = false; + Assert.That(compEx.called, Is.EqualTo(1)); + Assert.That(comp.called, Is.EqualTo(1)); + + // we have checks to make sure that it's only called once. + // let's see if they work. + identity.OnStartLocalPlayer(); + // same as before? + Assert.That(compEx.called, Is.EqualTo(1)); + // same as before? + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStopClient() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out NetworkDestroyCalledNetworkBehaviour comp); + + // call OnStopClient in identity + identity.OnStopClient(); + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStopClientException() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out NetworkDestroyExceptionNetworkBehaviour compEx, + out NetworkDestroyCalledNetworkBehaviour comp); + + // call OnStopClient in identity + // one component will throw an exception, but that shouldn't stop + // OnStopClient from being called in the second one + // exception will log an error + LogAssert.ignoreFailingMessages = true; + identity.OnStopClient(); + LogAssert.ignoreFailingMessages = false; + Assert.That(compEx.called, Is.EqualTo(1)); + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStopServer() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out StopServerCalledNetworkBehaviour comp); + + identity.OnStopServer(); + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void OnStopServerException() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, out StopServerExceptionNetworkBehaviour compEx); + + // make sure our test values are set to 0 + Assert.That(compEx.called, Is.EqualTo(0)); + + // call OnStopClient in identity + // one component will throw an exception, but that shouldn't stop + // OnStopClient from being called in the second one + // exception will log an error + LogAssert.ignoreFailingMessages = true; + identity.OnStopServer(); + LogAssert.ignoreFailingMessages = false; + Assert.That(compEx.called, Is.EqualTo(1)); + } + + [Test] + public void AddObserver() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // create some connections + NetworkConnectionToClient connection1 = new NetworkConnectionToClient(42); + NetworkConnectionToClient connection2 = new NetworkConnectionToClient(43); + + // AddObserver should return early if called before .observers was + // created + Assert.That(identity.observers, Is.Null); + // error log is expected + LogAssert.ignoreFailingMessages = true; + identity.AddObserver(connection1); + LogAssert.ignoreFailingMessages = false; + Assert.That(identity.observers, Is.Null); + + // call OnStartServer so that observers dict is created + identity.OnStartServer(); + + // call AddObservers + identity.AddObserver(connection1); + identity.AddObserver(connection2); + Assert.That(identity.observers.Count, Is.EqualTo(2)); + Assert.That(identity.observers.ContainsKey(connection1.connectionId)); + Assert.That(identity.observers[connection1.connectionId], Is.EqualTo(connection1)); + Assert.That(identity.observers.ContainsKey(connection2.connectionId)); + Assert.That(identity.observers[connection2.connectionId], Is.EqualTo(connection2)); + + // adding a duplicate connectionId shouldn't overwrite the original + NetworkConnectionToClient duplicate = new NetworkConnectionToClient(connection1.connectionId); + identity.AddObserver(duplicate); + Assert.That(identity.observers.Count, Is.EqualTo(2)); + Assert.That(identity.observers.ContainsKey(connection1.connectionId)); + Assert.That(identity.observers[connection1.connectionId], Is.EqualTo(connection1)); + Assert.That(identity.observers.ContainsKey(connection2.connectionId)); + Assert.That(identity.observers[connection2.connectionId], Is.EqualTo(connection2)); + } + + [Test] + public void ClearObservers() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // call OnStartServer so that observers dict is created + identity.OnStartServer(); + + // add some observers + identity.observers[42] = new NetworkConnectionToClient(42); + identity.observers[43] = new NetworkConnectionToClient(43); + + // call ClearObservers + identity.ClearObservers(); + Assert.That(identity.observers.Count, Is.EqualTo(0)); + } + + [Test] + public void ClearDirtyComponentsDirtyBits() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out OnStartClientTestNetworkBehaviour compA, + out OnStartClientTestNetworkBehaviour compB); + + // set syncintervals so one is always dirty, one is never dirty + compA.syncInterval = 0; + compB.syncInterval = Mathf.Infinity; + + // set components dirty bits + compA.SetSyncVarDirtyBit(0x0001); + compB.SetSyncVarDirtyBit(0x1001); + // dirty because interval reached and mask != 0 + Assert.That(compA.IsDirty(), Is.True); + // not dirty because syncinterval not reached + Assert.That(compB.IsDirty(), Is.False); + + // call identity.ClearDirtyComponentsDirtyBits + identity.ClearDirtyComponentsDirtyBits(); + // should be cleared now + Assert.That(compA.IsDirty(), Is.False); + // should be untouched + Assert.That(compB.IsDirty(), Is.False); + + // set compB syncinterval to 0 to check if the masks were untouched + // (if they weren't, then it should be dirty now) + compB.syncInterval = 0; + Assert.That(compB.IsDirty(), Is.True); + } + + [Test] + public void ClearAllComponentsDirtyBits() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity, + out OnStartClientTestNetworkBehaviour compA, + out OnStartClientTestNetworkBehaviour compB); + + // set syncintervals so one is always dirty, one is never dirty + compA.syncInterval = 0; + compB.syncInterval = Mathf.Infinity; + + // set components dirty bits + compA.SetSyncVarDirtyBit(0x0001); + compB.SetSyncVarDirtyBit(0x1001); + // dirty because interval reached and mask != 0 + Assert.That(compA.IsDirty(), Is.True); + // not dirty because syncinterval not reached + Assert.That(compB.IsDirty(), Is.False); + + // call identity.ClearAllComponentsDirtyBits + identity.ClearAllComponentsDirtyBits(); + // should be cleared now + Assert.That(compA.IsDirty(), Is.False); + // should be cleared now + Assert.That(compB.IsDirty(), Is.False); + + // set compB syncinterval to 0 to check if the masks were cleared + // (if they weren't, then it would still be dirty now) + compB.syncInterval = 0; + Assert.That(compB.IsDirty(), Is.False); + } + + [Test] + public void Reset() + { + CreateNetworked(out GameObject _, out NetworkIdentity identity); + + // modify it a bit + identity.isClient = true; + // creates .observers and generates a netId + identity.OnStartServer(); + identity.connectionToClient = new NetworkConnectionToClient(1); + identity.connectionToServer = new NetworkConnectionToServer(); + identity.observers[43] = new NetworkConnectionToClient(2); + + // mark for reset and reset + identity.Reset(); + Assert.That(identity.isServer, Is.False); + Assert.That(identity.isClient, Is.False); + Assert.That(identity.isLocalPlayer, Is.False); + Assert.That(identity.netId, Is.EqualTo(0)); + Assert.That(identity.connectionToClient, Is.Null); + Assert.That(identity.connectionToServer, Is.Null); + Assert.That(identity.hasAuthority, Is.False); + Assert.That(identity.observers, Is.Empty); + } + + [Test, Ignore("NetworkServerTest.SendCommand does it already")] + public void HandleCommand() {} + + [Test, Ignore("RpcTests do it already")] + public void HandleRpc() {} + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs.meta new file mode 100644 index 000000000..6ae479b04 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkIdentityTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e9b6d86d91cc74ce58e690371d4f3828 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkLoopTests.cs b/Assets/Mirror/Tests/Editor/NetworkLoopTests.cs new file mode 100644 index 000000000..eb77bd7e8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkLoopTests.cs @@ -0,0 +1,110 @@ +using NUnit.Framework; +#if UNITY_2019_1_OR_NEWER +using UnityEngine.LowLevel; +using UnityEngine.PlayerLoop; +#else +using UnityEngine.Experimental.LowLevel; +using UnityEngine.Experimental.PlayerLoop; +#endif + +namespace Mirror.Tests +{ + public class NetworkLoopTests + { + // all tests need a PlayerLoopSystem to work with + PlayerLoopSystem playerLoop; + + [SetUp] + public void SetUp() + { + // we get the main player loop to work with. + // we don't actually set it. no need to. + playerLoop = PlayerLoop.GetDefaultPlayerLoop(); + } + + // simple test to see if it finds and adds to EarlyUpdate() loop + [Test] + public void AddToPlayerLoop_EarlyUpdate_Beginning() + { + void Function() {} + + // add our function + bool result = NetworkLoop.AddToPlayerLoop(Function, typeof(NetworkLoopTests), ref playerLoop, typeof(EarlyUpdate), NetworkLoop.AddMode.Beginning); + Assert.That(result, Is.True); + + // was it added to the beginning? + int index = NetworkLoop.FindPlayerLoopEntryIndex(Function, playerLoop, typeof(EarlyUpdate)); + Assert.That(index, Is.EqualTo(0)); + } + + [Test] + public void AddToPlayerLoop_EarlyUpdate_End() + { + void Function() {} + + // add our function + bool result = NetworkLoop.AddToPlayerLoop(Function, typeof(NetworkLoopTests), ref playerLoop, typeof(EarlyUpdate), NetworkLoop.AddMode.End); + Assert.That(result, Is.True); + + // was it added to the end? we don't know the exact index, but it should be >0 + int index = NetworkLoop.FindPlayerLoopEntryIndex(Function, playerLoop, typeof(EarlyUpdate)); + Assert.That(index, Is.GreaterThan(0)); + } + + [Test] + public void AddToPlayerLoop_Update_Beginning() + { + void Function() {} + + // add our function + bool result = NetworkLoop.AddToPlayerLoop(Function, typeof(NetworkLoopTests), ref playerLoop, typeof(Update), NetworkLoop.AddMode.Beginning); + Assert.That(result, Is.True); + + // was it added to the beginning? + int index = NetworkLoop.FindPlayerLoopEntryIndex(Function, playerLoop, typeof(Update)); + Assert.That(index, Is.EqualTo(0)); + } + + [Test] + public void AddToPlayerLoop_Update_End() + { + void Function() {} + + // add our function + bool result = NetworkLoop.AddToPlayerLoop(Function, typeof(NetworkLoopTests), ref playerLoop, typeof(Update), NetworkLoop.AddMode.End); + Assert.That(result, Is.True); + + // was it added to the end? we don't know the exact index, but it should be >0 + int index = NetworkLoop.FindPlayerLoopEntryIndex(Function, playerLoop, typeof(Update)); + Assert.That(index, Is.GreaterThan(0)); + } + + [Test] + public void AddToPlayerLoop_PreLateUpdate_Beginning() + { + void Function() {} + + // add our function + bool result = NetworkLoop.AddToPlayerLoop(Function, typeof(NetworkLoopTests), ref playerLoop, typeof(PreLateUpdate), NetworkLoop.AddMode.Beginning); + Assert.That(result, Is.True); + + // was it added to the beginning? + int index = NetworkLoop.FindPlayerLoopEntryIndex(Function, playerLoop, typeof(PreLateUpdate)); + Assert.That(index, Is.EqualTo(0)); + } + + [Test] + public void AddToPlayerLoop_PreLateUpdate_End() + { + void Function() {} + + // add our function + bool result = NetworkLoop.AddToPlayerLoop(Function, typeof(NetworkLoopTests), ref playerLoop, typeof(PreLateUpdate), NetworkLoop.AddMode.End); + Assert.That(result, Is.True); + + // was it added to the end? we don't know the exact index, but it should be >0 + int index = NetworkLoop.FindPlayerLoopEntryIndex(Function, playerLoop, typeof(PreLateUpdate)); + Assert.That(index, Is.GreaterThan(0)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkLoopTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkLoopTests.cs.meta new file mode 100644 index 000000000..4fb7e3e0c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkLoopTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: eedb822be9a941428259caf9f707bb45 +timeCreated: 1614573407 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs b/Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs new file mode 100644 index 000000000..21702edee --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs @@ -0,0 +1,39 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + class NetworkManagerOnServerDisconnect : NetworkManager + { + public int called; + public override void OnServerDisconnect(NetworkConnection conn) + { + base.OnServerDisconnect(conn); + ++called; + } +} + + [TestFixture] + public class NetworkManagerStopHostOnServerDisconnectTest : MirrorEditModeTest + { + NetworkManagerOnServerDisconnect manager; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + manager = transport.gameObject.AddComponent(); + } + + // test to prevent https://github.com/vis2k/Mirror/issues/1515 + [Test] + public void StopHostCallsOnServerDisconnectForHostClient() + { + // OnServerDisconnect is always called when a client disconnects. + // it should also be called for the host client when we stop the host + Assert.That(manager.called, Is.EqualTo(0)); + manager.StartHost(); + manager.StopHost(); + Assert.That(manager.called, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs.meta new file mode 100644 index 000000000..b21084477 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkManagerStopHostOnServerDisconnectTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2f583081473a64b92b971678e571382a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkManagerTest.cs b/Assets/Mirror/Tests/Editor/NetworkManagerTest.cs new file mode 100644 index 000000000..6d912a9e3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkManagerTest.cs @@ -0,0 +1,148 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + [TestFixture] + public class NetworkManagerTest : MirrorEditModeTest + { + GameObject gameObject; + NetworkManager manager; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + gameObject = transport.gameObject; + manager = gameObject.AddComponent(); + } + + [Test] + public void StartServerTest() + { + Assert.That(NetworkServer.active, Is.False); + + manager.StartServer(); + + Assert.That(manager.mode == NetworkManagerMode.ServerOnly); + Assert.That(NetworkServer.active, Is.True); + } + + [Test] + public void StopServerTest() + { + manager.StartServer(); + manager.StopServer(); + + Assert.That(manager.mode == NetworkManagerMode.Offline); + } + + [Test] + public void StartClientTest() + { + manager.StartClient(); + + Assert.That(manager.mode == NetworkManagerMode.ClientOnly); + + manager.StopClient(); + } + + [Test] + public void StopClientTest() + { + manager.StartClient(); + manager.StopClient(); + + Assert.That(manager.mode == NetworkManagerMode.Offline); + } + + [Test] + public void StartHostTest() + { + manager.StartHost(); + + Assert.That(manager.mode == NetworkManagerMode.Host); + Assert.That(NetworkServer.active, Is.True); + Assert.That(NetworkClient.active, Is.True); + } + + [Test] + public void StopHostTest() + { + manager.StartHost(); + manager.StopHost(); + + Assert.That(manager.mode == NetworkManagerMode.Offline); + Assert.That(NetworkServer.active, Is.False); + Assert.That(NetworkClient.active, Is.False); + } + + [Test] + public void ShutdownTest() + { + manager.StartClient(); + NetworkManager.ResetStatics(); + + Assert.That(NetworkManager.startPositions.Count, Is.Zero); + Assert.That(NetworkManager.startPositionIndex, Is.Zero); + Assert.That(NetworkManager.clientReadyConnection, Is.Null); + Assert.That(NetworkManager.loadingSceneAsync, Is.Null); + Assert.That(NetworkManager.singleton, Is.Null); + Assert.That(NetworkManager.networkSceneName, Is.Empty); + } + + [Test] + public void RegisterStartPositionTest() + { + Assert.That(NetworkManager.startPositions.Count, Is.Zero); + + NetworkManager.RegisterStartPosition(gameObject.transform); + Assert.That(NetworkManager.startPositions.Count, Is.EqualTo(1)); + Assert.That(NetworkManager.startPositions, Has.Member(gameObject.transform)); + + NetworkManager.UnRegisterStartPosition(gameObject.transform); + } + + [Test] + public void UnRegisterStartPositionTest() + { + Assert.That(NetworkManager.startPositions.Count, Is.Zero); + + NetworkManager.RegisterStartPosition(gameObject.transform); + Assert.That(NetworkManager.startPositions.Count, Is.EqualTo(1)); + Assert.That(NetworkManager.startPositions, Has.Member(gameObject.transform)); + + NetworkManager.UnRegisterStartPosition(gameObject.transform); + Assert.That(NetworkManager.startPositions.Count, Is.Zero); + } + + [Test] + public void GetStartPositionTest() + { + Assert.That(NetworkManager.startPositions.Count, Is.Zero); + + NetworkManager.RegisterStartPosition(gameObject.transform); + Assert.That(NetworkManager.startPositions.Count, Is.EqualTo(1)); + Assert.That(NetworkManager.startPositions, Has.Member(gameObject.transform)); + + Assert.That(manager.GetStartPosition(), Is.SameAs(gameObject.transform)); + + NetworkManager.UnRegisterStartPosition(gameObject.transform); + } + + [Test] + public void StartClientUriTest() + { + UriBuilder uriBuilder = new UriBuilder + { + Host = "localhost", + Scheme = "local" + }; + manager.StartClient(uriBuilder.Uri); + + Assert.That(manager.mode, Is.EqualTo(NetworkManagerMode.ClientOnly)); + Assert.That(manager.networkAddress, Is.EqualTo(uriBuilder.Uri.Host)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkManagerTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkManagerTest.cs.meta new file mode 100644 index 000000000..ad10e831e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkManagerTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 11f1d5fee03c2764691201aabc5dda98 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkMessageTests.cs b/Assets/Mirror/Tests/Editor/NetworkMessageTests.cs new file mode 100644 index 000000000..50e6cea19 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkMessageTests.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; + +namespace Mirror.Tests.MessageTests +{ + struct TestMessage : NetworkMessage + { + public int IntValue; + public string StringValue; + public double DoubleValue; + + public TestMessage(int i, string s, double d) + { + IntValue = i; + StringValue = s; + DoubleValue = d; + } + + public void Deserialize(NetworkReader reader) + { + IntValue = reader.ReadInt(); + StringValue = reader.ReadString(); + DoubleValue = reader.ReadDouble(); + } + + public void Serialize(NetworkWriter writer) + { + writer.WriteInt(IntValue); + writer.WriteString(StringValue); + writer.WriteDouble(DoubleValue); + } + } + + struct StructWithEmptyMethodMessage : NetworkMessage + { + public int IntValue; + public string StringValue; + public double DoubleValue; + } + + [TestFixture] + public class NetworkMessageTests + { + // with Serialize/Deserialize + [Test] + public void StructWithMethods() + { + byte[] bytes = MessagePackingTest.PackToByteArray(new TestMessage(1, "2", 3.3)); + TestMessage message = MessagePackingTest.UnpackFromByteArray(bytes); + + Assert.AreEqual(1, message.IntValue); + } + + // without Serialize/Deserialize. Weaver should handle it. + [Test] + public void StructWithEmptyMethods() + { + byte[] bytes = MessagePackingTest.PackToByteArray(new StructWithEmptyMethodMessage { IntValue = 1, StringValue = "2", DoubleValue = 3.3 }); + StructWithEmptyMethodMessage message = MessagePackingTest.UnpackFromByteArray(bytes); + + Assert.AreEqual(1, message.IntValue); + Assert.AreEqual("2", message.StringValue); + Assert.AreEqual(3.3, message.DoubleValue); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkMessageTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkMessageTests.cs.meta new file mode 100644 index 000000000..14117769c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkMessageTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ecf93fcf0386fee4e85f981d5ca9259d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs new file mode 100644 index 000000000..49bf1bd48 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs @@ -0,0 +1,84 @@ +using System.IO; +using NUnit.Framework; + +namespace Mirror.Tests +{ + // NetworkWriterTest already covers most cases for NetworkReader. + // only a few are left + [TestFixture] + public class NetworkReaderTest + { + /* uncomment if needed. commented for faster test workflow. this takes >3s. + [Test] + public void Benchmark() + { + // 10 million reads, Unity 2019.3, code coverage disabled + // 4049ms (+GC later) + byte[] bytes = { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C }; + for (int i = 0; i < 10000000; ++i) + { + ArraySegment segment = new ArraySegment(bytes); + NetworkReader reader = new NetworkReader(segment); + Vector3 value = reader.ReadVector3(); + } + } + */ + + [Test] + public void SetBuffer() + { + // start with an initial buffer + byte[] firstBuffer = {0xFF}; + NetworkReader reader = new NetworkReader(firstBuffer); + + // read one byte so we modify position + reader.ReadByte(); + + // set another buffer + byte[] secondBuffer = {0x42}; + reader.SetBuffer(secondBuffer); + + // was position reset? + Assert.That(reader.Position, Is.EqualTo(0)); + + // are we really on the second buffer now? + Assert.That(reader.ReadByte(), Is.EqualTo(0x42)); + } + + [Test] + public void Remaining() + { + byte[] bytes = {0x00, 0x01}; + NetworkReader reader = new NetworkReader(bytes); + Assert.That(reader.Remaining, Is.EqualTo(2)); + + reader.ReadByte(); + Assert.That(reader.Remaining, Is.EqualTo(1)); + + reader.ReadByte(); + Assert.That(reader.Remaining, Is.EqualTo(0)); + } + + [Test] + public void ReadBytesCountTooBigTest() + { + // calling ReadBytes with a count bigger than what is in Reader + // should throw an exception + byte[] bytes = { 0x00, 0x01 }; + + using (PooledNetworkReader reader = NetworkReaderPool.GetReader(bytes)) + { + try + { + byte[] result = reader.ReadBytes(bytes, bytes.Length + 1); + // BAD: IF WE GOT HERE, THEN NO EXCEPTION WAS THROWN + Assert.Fail(); + } + catch (EndOfStreamException) + { + // GOOD + } + } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs.meta new file mode 100644 index 000000000..ce21bed1d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkReaderTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3799af27efdf144909de8790851053a8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs new file mode 100644 index 000000000..32fa7838f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + [TestFixture] + public class NetworkReaderWriterTest + { + [Test] + public void TestIntWriterNotNull() + { + Assert.That(Writer.write, Is.Not.Null); + } + + [Test] + public void TestIntReaderNotNull() + { + Assert.That(Reader.read, Is.Not.Null); + } + + [Test] + public void TestAccessingCustomWriterAndReader() + { + NetworkWriter writer = new NetworkWriter(); + writer.Write(3); + NetworkReader reader = new NetworkReader(writer.ToArray()); + int copy = reader.Read(); + + Assert.That(copy, Is.EqualTo(3)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta new file mode 100644 index 000000000..1e4a29f9b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkReaderWriterTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4db12e3e883ac4c3aac177c638ee93e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs new file mode 100644 index 000000000..bc126e84d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs @@ -0,0 +1,1323 @@ +using System; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using System.Threading; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests +{ + struct TestMessage1 : NetworkMessage {} + + struct VariableSizedMessage : NetworkMessage + { + // weaver serializes byte[] wit WriteBytesAndSize + public byte[] payload; + // so payload := size - 4 + // then the message is exactly maxed size. + // + // NOTE: we have a LargerMaxMessageSize test which guarantees that + // variablesized + 1 is exactly transport.max + 1 + public VariableSizedMessage(int size) => payload = new byte[size - 4]; + } + + public class CommandTestNetworkBehaviour : NetworkBehaviour + { + // counter to make sure that it's called exactly once + public int called; + + [Command] + public void TestCommand() => ++called; + } + + public class RpcTestNetworkBehaviour : NetworkBehaviour + { + // counter to make sure that it's called exactly once + public int called; + // weaver generates this from [Rpc] + // but for tests we need to add it manually + public static void RpcGenerated(NetworkBehaviour comp, NetworkReader reader, NetworkConnection senderConnection) + { + ++((RpcTestNetworkBehaviour)comp).called; + } + } + + public class OnStartClientTestNetworkBehaviour : NetworkBehaviour + { + // counter to make sure that it's called exactly once + public int called; + public override void OnStartClient() => ++called; + } + + public class OnStopClientTestNetworkBehaviour : NetworkBehaviour + { + // counter to make sure that it's called exactly once + public int called; + public override void OnStopClient() => ++called; + } + + [TestFixture] + public class NetworkServerTest : MirrorEditModeTest + { + [Test] + public void IsActive() + { + Assert.That(NetworkServer.active, Is.False); + NetworkServer.Listen(1); + Assert.That(NetworkServer.active, Is.True); + NetworkServer.Shutdown(); + Assert.That(NetworkServer.active, Is.False); + } + + [Test] + public void MaxConnections() + { + // listen with maxconnections=1 + NetworkServer.Listen(1); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + + // connect first: should work + transport.OnServerConnected.Invoke(42); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + + // connect second: should fail + transport.OnServerConnected.Invoke(43); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + } + + [Test] + public void OnConnectedEventCalled() + { + // message handlers + bool connectCalled = false; + NetworkServer.OnConnectedEvent = conn => connectCalled = true; + + // listen & connect + NetworkServer.Listen(1); + transport.OnServerConnected.Invoke(42); + Assert.That(connectCalled, Is.True); + } + + [Test] + public void OnDisconnectedEventCalled() + { + // message handlers + bool disconnectCalled = false; + NetworkServer.OnDisconnectedEvent = conn => disconnectCalled = true; + + // listen & connect + NetworkServer.Listen(1); + transport.OnServerConnected.Invoke(42); + + // disconnect + transport.OnServerDisconnected.Invoke(42); + Assert.That(disconnectCalled, Is.True); + } + + [Test] + public void ConnectionsDict() + { + // listen + NetworkServer.Listen(2); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + + // connect first + transport.OnServerConnected.Invoke(42); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + Assert.That(NetworkServer.connections.ContainsKey(42), Is.True); + + // connect second + transport.OnServerConnected.Invoke(43); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(2)); + Assert.That(NetworkServer.connections.ContainsKey(43), Is.True); + + // disconnect second + transport.OnServerDisconnected.Invoke(43); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + Assert.That(NetworkServer.connections.ContainsKey(42), Is.True); + + // disconnect first + transport.OnServerDisconnected.Invoke(42); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + } + + [Test] + public void OnConnectedOnlyAllowsNonZeroConnectionIds() + { + // OnConnected should only allow connectionIds >= 0 + // 0 is for local player + // <0 is never used + + // listen + NetworkServer.Listen(2); + + // connect with connectionId == 0 should fail + // (it will show an error message, which is expected) + LogAssert.ignoreFailingMessages = true; + transport.OnServerConnected.Invoke(0); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + LogAssert.ignoreFailingMessages = false; + } + + [Test] + public void ConnectDuplicateConnectionIds() + { + // listen + NetworkServer.Listen(2); + + // connect first + transport.OnServerConnected.Invoke(42); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + NetworkConnectionToClient original = NetworkServer.connections[42]; + + // connect duplicate - shouldn't overwrite first one + transport.OnServerConnected.Invoke(42); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + Assert.That(NetworkServer.connections[42], Is.EqualTo(original)); + } + + [Test] + public void SetLocalConnection() + { + // listen + NetworkServer.Listen(1); + + // set local connection + LocalConnectionToClient localConnection = new LocalConnectionToClient(); + NetworkServer.SetLocalConnection(localConnection); + Assert.That(NetworkServer.localConnection, Is.EqualTo(localConnection)); + } + + [Test] + public void SetLocalConnection_PreventsOverwrite() + { + // listen + NetworkServer.Listen(1); + + // set local connection + LocalConnectionToClient localConnection = new LocalConnectionToClient(); + NetworkServer.SetLocalConnection(localConnection); + + // try to overwrite it, which should not work + // (it will show an error message, which is expected) + LogAssert.ignoreFailingMessages = true; + NetworkServer.SetLocalConnection(new LocalConnectionToClient()); + Assert.That(NetworkServer.localConnection, Is.EqualTo(localConnection)); + LogAssert.ignoreFailingMessages = false; + } + + [Test] + public void RemoveLocalConnection() + { + // listen + NetworkServer.Listen(1); + + // set local connection + CreateLocalConnectionPair(out LocalConnectionToClient connectionToClient, out _); + NetworkServer.SetLocalConnection(connectionToClient); + + // remove local connection + NetworkServer.RemoveLocalConnection(); + Assert.That(NetworkServer.localConnection, Is.Null); + } + + [Test] + public void LocalClientActive() + { + // listen + NetworkServer.Listen(1); + Assert.That(NetworkServer.localClientActive, Is.False); + + // set local connection + NetworkServer.SetLocalConnection(new LocalConnectionToClient()); + Assert.That(NetworkServer.localClientActive, Is.True); + } + + [Test] + public void AddConnection() + { + // listen + NetworkServer.Listen(1); + + // add first connection + NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42); + Assert.That(NetworkServer.AddConnection(conn42), Is.True); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42)); + + // add second connection + NetworkConnectionToClient conn43 = new NetworkConnectionToClient(43); + Assert.That(NetworkServer.AddConnection(conn43), Is.True); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(2)); + Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42)); + Assert.That(NetworkServer.connections[43], Is.EqualTo(conn43)); + } + + [Test] + public void AddConnection_PreventsDuplicates() + { + // listen + NetworkServer.Listen(1); + + // add a connection + NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42); + Assert.That(NetworkServer.AddConnection(conn42), Is.True); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42)); + + // add duplicate connectionId + NetworkConnectionToClient connDup = new NetworkConnectionToClient(42); + Assert.That(NetworkServer.AddConnection(connDup), Is.False); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + Assert.That(NetworkServer.connections[42], Is.EqualTo(conn42)); + } + + [Test] + public void RemoveConnection() + { + // listen + NetworkServer.Listen(1); + + // add connection + NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42); + Assert.That(NetworkServer.AddConnection(conn42), Is.True); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + + // remove connection + Assert.That(NetworkServer.RemoveConnection(42), Is.True); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + } + + [Test] + public void DisconnectAllTest_RemoteConnection() + { + // listen + NetworkServer.Listen(1); + + // add connection + NetworkConnectionToClient conn42 = new NetworkConnectionToClient(42); + NetworkServer.AddConnection(conn42); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + + // disconnect all connections + NetworkServer.DisconnectAll(); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + } + + [Test] + public void DisconnectAllTest_LocalConnection() + { + // listen + NetworkServer.Listen(1); + + // set local connection + LocalConnectionToClient localConnection = new LocalConnectionToClient(); + NetworkServer.SetLocalConnection(localConnection); + + // disconnect all connections should remove local connection + NetworkServer.DisconnectAll(); + Assert.That(NetworkServer.localConnection, Is.Null); + } + + // test to reproduce https://github.com/vis2k/Mirror/pull/2797 + [Test] + public void Destroy_HostMode_CallsOnStopAuthority() + { + // listen & connect a HOST client + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // spawn a player(!) object + // otherwise client wouldn't receive spawn / authority messages + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity player, + out StopAuthorityCalledNetworkBehaviour comp, + NetworkServer.localConnection); + + // need to have authority for this test + Assert.That(player.hasAuthority, Is.True); + + // destroy and ignore 'Destroy called in Edit mode' error + LogAssert.ignoreFailingMessages = true; + NetworkServer.Destroy(player.gameObject); + LogAssert.ignoreFailingMessages = false; + + // destroy should call OnStopAuthority + Assert.That(comp.called, Is.EqualTo(1)); + } + + // send a message all the way from client to server + [Test] + public void Send_ClientToServerMessage() + { + // register a message handler + int called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send message & process + NetworkClient.Send(new TestMessage1()); + ProcessMessages(); + + // did it get through? + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void Send_ServerToClientMessage() + { + // register a message handler + int called = 0; + NetworkClient.RegisterHandler(msg => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send message & process + connectionToClient.Send(new TestMessage1()); + ProcessMessages(); + + // did it get through? + Assert.That(called, Is.EqualTo(1)); + } + + // guarantee that exactly max packet size messages work + [Test] + public void Send_ClientToServerMessage_MaxMessageSize() + { + // register a message handler + int called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send message & process + int max = MessagePacking.MaxContentSize; + NetworkClient.Send(new VariableSizedMessage(max)); + ProcessMessages(); + + // did it get through? + Assert.That(called, Is.EqualTo(1)); + } + + // guarantee that exactly max packet size messages work + [Test] + public void Send_ServerToClientMessage_MaxMessageSize() + { + // register a message handler + int called = 0; + NetworkClient.RegisterHandler(msg => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send message & process + int max = MessagePacking.MaxContentSize; + connectionToClient.Send(new VariableSizedMessage(max)); + ProcessMessages(); + + // did it get through? + Assert.That(called, Is.EqualTo(1)); + } + + // guarantee that exactly max message size + 1 doesn't work anymore + [Test] + public void Send_ClientToServerMessage_LargerThanMaxMessageSize() + { + // register a message handler + int called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // calculate max := transport.max - message header + + // send message & process + int transportMax = transport.GetMaxPacketSize(Channels.Reliable); + int messageMax = MessagePacking.MaxContentSize; + LogAssert.Expect(LogType.Error, $"NetworkConnection.ValidatePacketSize: cannot send packet larger than {transportMax} bytes, was {transportMax + 1} bytes"); + NetworkClient.Send(new VariableSizedMessage(messageMax + 1)); + ProcessMessages(); + + // should be too big to send + Assert.That(called, Is.EqualTo(0)); + } + + // guarantee that exactly max message size + 1 doesn't work anymore + [Test] + public void Send_ServerToClientMessage_LargerThanMaxMessageSize() + { + // register a message handler + int called = 0; + NetworkClient.RegisterHandler(msg => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send message & process + int transportMax = transport.GetMaxPacketSize(Channels.Reliable); + int messageMax = MessagePacking.MaxContentSize; + LogAssert.Expect(LogType.Error, $"NetworkConnection.ValidatePacketSize: cannot send packet larger than {transportMax} bytes, was {transportMax + 1} bytes"); + connectionToClient.Send(new VariableSizedMessage(messageMax + 1)); + ProcessMessages(); + + // should be too big to send + Assert.That(called, Is.EqualTo(0)); + } + + // transport recommends a max batch size. + // but we support up to max packet size. + // for example, with KCP it makes sense to always send MTU sized batches. + // but we can send up to 144 KB messages. + // => make sure this works. it's a special path in the code and used to + // cause a bug in uMMORPG where SpawnMessage would be > MTU, the + // timestamp would not be included because > max batch, hence client + // couldn't parse it properly. + [Test] + public void Send_ClientToServerMessage_LargerThanBatchThreshold() + { + // register a message handler + int called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send message & process + int threshold = transport.GetBatchThreshold(Channels.Reliable); + NetworkClient.Send(new VariableSizedMessage(threshold + 1)); + ProcessMessages(); + + // did it get through? + Assert.That(called, Is.EqualTo(1)); + } + + // transport recommends a max batch size. + // but we support up to max packet size. + // for example, with KCP it makes sense to always send MTU sized batches. + // but we can send up to 144 KB messages. + // => make sure this works. it's a special path in the code and used to + // cause a bug in uMMORPG where SpawnMessage would be > MTU, the + // timestamp would not be included because > max batch, hence client + // couldn't parse it properly. + [Test] + public void Send_ServerToClientMessage_LargerThanBatchThreshold() + { + // register handler + int called = 0; + NetworkClient.RegisterHandler(msg => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send large message & process + int threshold = transport.GetBatchThreshold(Channels.Reliable); + connectionToClient.Send(new VariableSizedMessage(threshold + 1)); + ProcessMessages(); + + // did it get through? + Assert.That(called, Is.EqualTo(1)); + } + + // there used to be a data race where messages > batch threshold would + // be sent directly, instead of being flushed at the end of the frame + // like all the smaller messages. + // make sure this never happens again. + [Test] + public void Send_ClientToServerMessage_LargerThanBatchThreshold_SentInOrder() + { + // register two message handlers + List received = new List(); + NetworkServer.RegisterHandler((conn, msg) => received.Add("smol"), false); + NetworkServer.RegisterHandler((conn, msg) => received.Add("big"), false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send small message first + NetworkClient.Send(new TestMessage1()); + + // send large message + int threshold = transport.GetBatchThreshold(Channels.Reliable); + NetworkClient.Send(new VariableSizedMessage(threshold + 1)); + + // process everything + ProcessMessages(); + + // both arrived, and small arrived before large? + Assert.That(received.Count, Is.EqualTo(2)); + Assert.That(received[0], Is.EqualTo("smol")); + Assert.That(received[1], Is.EqualTo("big")); + } + + // there used to be a data race where messages > batch threshold would + // be sent directly, instead of being flushed at the end of the frame + // like all the smaller messages. + // make sure this never happens again. + [Test] + public void Send_ServerToClientMessage_LargerThanBatchThreshold_SentInOrder() + { + // register two message handlers + List received = new List(); + NetworkClient.RegisterHandler(msg => received.Add("smol"), false); + NetworkClient.RegisterHandler(msg => received.Add("big"), false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send small message first + connectionToClient.Send(new TestMessage1()); + + // send large message + int threshold = transport.GetBatchThreshold(Channels.Reliable); + connectionToClient.Send(new VariableSizedMessage(threshold + 1)); + + // process everything + ProcessMessages(); + + // both arrived, and small arrived before large? + Assert.That(received.Count, Is.EqualTo(2)); + Assert.That(received[0], Is.EqualTo("smol")); + Assert.That(received[1], Is.EqualTo("big")); + } + + // make sure NetworkConnection.remoteTimeStamp is always the time on the + // remote end when the message was sent + [Test] + public void Send_ClientToServerMessage_SetsRemoteTimeStamp() + { + // register a message handler + int called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send message + NetworkClient.Send(new TestMessage1()); + + // remember current time & update NetworkClient IMMEDIATELY so the + // batch is finished with timestamp. + double sendTime = NetworkTime.localTime; + NetworkClient.NetworkLateUpdate(); + + // let some time pass before processing + const int waitTime = 100; + Thread.Sleep(waitTime); + ProcessMessages(); + + // is the remote timestamp set to when we sent it? + // remember the time when we sent the message + // (within 1/10th of the time we waited. we need some tolerance + // because we don't capture NetworkTime.localTime exactly when we + // finish the batch. but the difference should not be > 'waitTime') + Assert.That(called, Is.EqualTo(1)); + Assert.That(connectionToClient.remoteTimeStamp, Is.EqualTo(sendTime).Within(waitTime / 10)); + } + + // test to avoid https://github.com/vis2k/Mirror/issues/2882 + // messages in a batch aren't length prefixed. + // if we can't read one, we need to warn and disconnect. + // otherwise it overlaps to the next message and causes undefined behaviour. + [Test] + public void Send_ClientToServerMessage_UnknownMessageIdDisconnects() + { + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send a message without a registered handler + NetworkClient.Send(new TestMessage1()); + ProcessMessages(); + + // should have been disconnected + Assert.That(NetworkServer.connections.ContainsKey(connectionToClient.connectionId), Is.False); + } + + [Test] + public void Send_ServerToClientMessage_SetsRemoteTimeStamp() + { + // register a message handler + int called = 0; + NetworkClient.RegisterHandler(msg => ++called, false); + + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send message + connectionToClient.Send(new TestMessage1()); + + // remember current time & update NetworkClient IMMEDIATELY so the + // batch is finished with timestamp. + double sendTime = NetworkTime.localTime; + NetworkServer.NetworkLateUpdate(); + + // let some time pass before processing + const int waitTime = 100; + Thread.Sleep(waitTime); + ProcessMessages(); + + // is the remote timestamp set to when we sent it? + // remember the time when we sent the message + // (within 1/10th of the time we waited. we need some tolerance + // because we don't capture NetworkTime.localTime exactly when we + // finish the batch. but the difference should not be > 'waitTime') + Assert.That(called, Is.EqualTo(1)); + Assert.That(NetworkClient.connection.remoteTimeStamp, Is.EqualTo(sendTime).Within(waitTime / 10)); + } + + // test to avoid https://github.com/vis2k/Mirror/issues/2882 + // messages in a batch aren't length prefixed. + // if we can't read one, we need to warn and disconnect. + // otherwise it overlaps to the next message and causes undefined behaviour. + [Test] + public void Send_ServerToClientMessage_UnknownMessageIdDisconnects() + { + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient connectionToClient); + + // send a message without a registered handler + connectionToClient.Send(new TestMessage1()); + ProcessMessages(); + + // should have been disconnected + Assert.That(NetworkClient.active, Is.False); + } + + [Test] + public void OnDataReceivedInvalidConnectionId() + { + // register a message handler + int called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++called, false); + + // listen + NetworkServer.Listen(1); + + // serialize a test message into an arraysegment + byte[] message = MessagePackingTest.PackToByteArray(new TestMessage1()); + + // call transport.OnDataReceived with an invalid connectionId + // an error log is expected. + LogAssert.ignoreFailingMessages = true; + transport.OnServerDataReceived.Invoke(42, new ArraySegment(message), 0); + LogAssert.ignoreFailingMessages = false; + + // message handler should never be called + Assert.That(called, Is.EqualTo(0)); + } + + [Test] + public void SetClientReadyAndNotReady() + { + CreateLocalConnectionPair(out LocalConnectionToClient connectionToClient, out _); + Assert.That(connectionToClient.isReady, Is.False); + + NetworkServer.SetClientReady(connectionToClient); + Assert.That(connectionToClient.isReady, Is.True); + + NetworkServer.SetClientNotReady(connectionToClient); + Assert.That(connectionToClient.isReady, Is.False); + } + + [Test] + public void SetAllClientsNotReady() + { + // add first ready client + CreateLocalConnectionPair(out LocalConnectionToClient first, out _); + first.isReady = true; + NetworkServer.connections[42] = first; + + // add second ready client + CreateLocalConnectionPair(out LocalConnectionToClient second, out _); + second.isReady = true; + NetworkServer.connections[43] = second; + + // set all not ready + NetworkServer.SetAllClientsNotReady(); + Assert.That(first.isReady, Is.False); + Assert.That(second.isReady, Is.False); + } + + [Test] + public void ReadyMessageSetsClientReady() + { + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient); + Assert.That(connectionToClient.isReady, Is.True); + } + + // simply send a [Command] from client to server + [Test] + public void SendCommand() + { + // listen & connect + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // add an identity with two networkbehaviour components + // spawned, otherwise command handler won't find it in .spawned. + // WITH OWNER = WITH AUTHORITY + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out CommandTestNetworkBehaviour comp, NetworkServer.localConnection); + + // call the command + comp.TestCommand(); + ProcessMessages(); + Assert.That(comp.called, Is.EqualTo(1)); + } + + // send a [Command] to an entity with TWO command components. + // make sure the correct one is called. + [Test] + public void SendCommand_CalledOnCorrectComponent() + { + // listen & connect + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // add an identity with two networkbehaviour components. + // spawned, otherwise command handler won't find it in .spawned. + // WITH OWNER = WITH AUTHORITY + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out CommandTestNetworkBehaviour comp0, out CommandTestNetworkBehaviour comp1, NetworkServer.localConnection); + + // call the command + comp1.TestCommand(); + ProcessMessages(); + Assert.That(comp0.called, Is.EqualTo(0)); + Assert.That(comp1.called, Is.EqualTo(1)); + } + + [Test] + public void SendCommand_OnlyAllowedOnOwnedObjects() + { + // listen & connect + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // add an identity with two networkbehaviour components + // spawned, otherwise command handler won't find it in .spawned. + // WITH OWNER = WITH AUTHORITY + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity identity, out CommandTestNetworkBehaviour comp, NetworkServer.localConnection); + + // change identity's owner connection so we can't call [Commands] on it + identity.connectionToClient = new LocalConnectionToClient(); + + // call the command + comp.TestCommand(); + ProcessMessages(); + Assert.That(comp.called, Is.EqualTo(0)); + } + + [Test] + public void SendCommand_RequiresAuthority() + { + // listen & connect + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // add an identity with two networkbehaviour components + // spawned, otherwise command handler won't find it in .spawned. + // WITHOUT OWNER = WITHOUT AUTHORITY for this test + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out CommandTestNetworkBehaviour comp); + + // call the command + comp.TestCommand(); + ProcessMessages(); + Assert.That(comp.called, Is.EqualTo(0)); + } + + [Test] + public void ActivateHostSceneCallsOnStartClient() + { + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out _); + + // spawn identity with a networkbehaviour. + // (needs to be in .spawned for ActivateHostScene) + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverIdentity, out OnStartClientTestNetworkBehaviour serverComp, + out _, out _, out _); + + // ActivateHostScene calls OnStartClient for spawned objects where + // isClient is still false. set it to false first. + serverIdentity.isClient = false; + NetworkServer.ActivateHostScene(); + + // was OnStartClient called for all .spawned networkidentities? + Assert.That(serverComp.called, Is.EqualTo(1)); + } + + [Test] + public void SendToAll() + { + // message handler + int called = 0; + NetworkClient.RegisterHandler(msg => ++called, false); + + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send & process + NetworkServer.SendToAll(new TestMessage1()); + ProcessMessages(); + + // called? + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void UnregisterHandler() + { + // RegisterHandler(conn, msg) variant + int variant1Called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++variant1Called, false); + + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send a message, check if it was handled + NetworkClient.Send(new TestMessage1()); + ProcessMessages(); + Assert.That(variant1Called, Is.EqualTo(1)); + + // unregister, send again, should not be called again + NetworkServer.UnregisterHandler(); + NetworkClient.Send(new TestMessage1()); + ProcessMessages(); + Assert.That(variant1Called, Is.EqualTo(1)); + } + + [Test] + public void ClearHandler() + { + // RegisterHandler(conn, msg) variant + int variant1Called = 0; + NetworkServer.RegisterHandler((conn, msg) => ++variant1Called, false); + + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlocking(out _); + + // send a message, check if it was handled + NetworkClient.Send(new TestMessage1()); + ProcessMessages(); + Assert.That(variant1Called, Is.EqualTo(1)); + + // clear handlers, send again, should not be called again + NetworkServer.ClearHandlers(); + NetworkClient.Send(new TestMessage1()); + ProcessMessages(); + Assert.That(variant1Called, Is.EqualTo(1)); + } + + [Test] + public void GetNetworkIdentity() + { + // create a GameObject with NetworkIdentity + CreateNetworked(out GameObject go, out NetworkIdentity identity); + + // GetNetworkIdentity + bool result = NetworkServer.GetNetworkIdentity(go, out NetworkIdentity value); + Assert.That(result, Is.True); + Assert.That(value, Is.EqualTo(identity)); + } + + [Test] + public void GetNetworkIdentity_ErrorIfNotFound() + { + // create a GameObject without NetworkIdentity + CreateGameObject(out GameObject goWithout); + + // GetNetworkIdentity for GO without identity + LogAssert.Expect(LogType.Error, $"GameObject {goWithout.name} doesn't have NetworkIdentity."); + bool result = NetworkServer.GetNetworkIdentity(goWithout, out NetworkIdentity value); + Assert.That(result, Is.False); + Assert.That(value, Is.Null); + } + + [Test] + public void ShowForConnection() + { + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient); + + // overwrite spawn message handler + int called = 0; + NetworkClient.ReplaceHandler(msg => ++called, false); + + // create a gameobject and networkidentity and some unique values + CreateNetworked(out GameObject _, out NetworkIdentity identity); + identity.connectionToClient = connectionToClient; + + // call ShowForConnection + NetworkServer.ShowForConnection(identity, connectionToClient); + ProcessMessages(); + Assert.That(called, Is.EqualTo(1)); + + // destroy manually to avoid 'Destroy can't be called in edit mode' + GameObject.DestroyImmediate(identity.gameObject); + } + + [Test] + public void ShowForConnection_OnlyWorksIfReady() + { + // listen & connect + // DO NOT set ready this time + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticated(out NetworkConnectionToClient connectionToClient); + + // overwrite spawn message handler + int called = 0; + NetworkClient.ReplaceHandler(msg => ++called, false); + + // create a gameobject and networkidentity and some unique values + CreateNetworked(out GameObject _, out NetworkIdentity identity); + identity.connectionToClient = connectionToClient; + + // call ShowForConnection - should not work if not ready + NetworkServer.ShowForConnection(identity, connectionToClient); + ProcessMessages(); + Assert.That(called, Is.EqualTo(0)); + + // destroy manually to avoid 'Destroy can't be called in edit mode' + GameObject.DestroyImmediate(identity.gameObject); + } + + [Test] + public void HideForConnection() + { + // listen & connect + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out NetworkConnectionToClient connectionToClient); + + // overwrite spawn message handler + int called = 0; + NetworkClient.ReplaceHandler(msg => ++called, false); + + // create a gameobject and networkidentity and some unique values + CreateNetworked(out GameObject _, out NetworkIdentity identity); + identity.connectionToClient = connectionToClient; + + // call HideForConnection + NetworkServer.HideForConnection(identity, connectionToClient); + ProcessMessages(); + Assert.That(called, Is.EqualTo(1)); + + // destroy manually to avoid 'Destroy can't be called in edit mode' + GameObject.DestroyImmediate(identity.gameObject); + } + + [Test] + public void ValidateSceneObject() + { + // create a gameobject and networkidentity + CreateNetworked(out GameObject go, out NetworkIdentity identity); + identity.sceneId = 42; + + // should be valid as long as it has a sceneId + Assert.That(NetworkServer.ValidateSceneObject(identity), Is.True); + + // shouldn't be valid with 0 sceneID + identity.sceneId = 0; + Assert.That(NetworkServer.ValidateSceneObject(identity), Is.False); + identity.sceneId = 42; + + // shouldn't be valid for certain hide flags + go.hideFlags = HideFlags.NotEditable; + Assert.That(NetworkServer.ValidateSceneObject(identity), Is.False); + go.hideFlags = HideFlags.HideAndDontSave; + Assert.That(NetworkServer.ValidateSceneObject(identity), Is.False); + } + + [Test] + public void SpawnObjects() + { + // create a scene object and set inactive before spawning + CreateNetworked(out GameObject go, out NetworkIdentity identity); + identity.sceneId = 42; + go.SetActive(false); + + // create a NON scene object and set inactive before spawning + CreateNetworked(out GameObject go2, out NetworkIdentity identity2); + identity2.sceneId = 0; + go2.SetActive(false); + + // start server + NetworkServer.Listen(1); + + // SpawnObjects() should return true and activate the scene object + Assert.That(NetworkServer.SpawnObjects(), Is.True); + Assert.That(go.activeSelf, Is.True); + Assert.That(go2.activeSelf, Is.False); + + // reset isServer to avoid Destroy instead of DestroyImmediate + identity.isServer = false; + identity2.isServer = false; + } + + [Test] + public void SpawnObjects_OnlyIfServerActive() + { + // calling SpawnObjects while server isn't active should do nothing + Assert.That(NetworkServer.SpawnObjects(), Is.False); + } + + [Test] + public void UnSpawn() + { + // create scene object with valid netid and set active + CreateNetworked(out GameObject go, out NetworkIdentity identity); + identity.sceneId = 42; + identity.netId = 123; + go.SetActive(true); + + // unspawn should reset netid + NetworkServer.UnSpawn(go); + Assert.That(identity.netId, Is.Zero); + } + + [Test] + public void UnSpawnAndClearAuthority() + { + // create scene object with valid netid and set active + CreateNetworked(out GameObject go, out NetworkIdentity identity, out StartAuthorityCalledNetworkBehaviour compStart, out StopAuthorityCalledNetworkBehaviour compStop); + identity.sceneId = 42; + identity.netId = 123; + go.SetActive(true); + + // set authority from false to true, which should call OnStartAuthority + identity.hasAuthority = true; + identity.NotifyAuthority(); + + // shouldn't be touched + Assert.That(identity.hasAuthority, Is.True); + // start should be called + Assert.That(compStart.called, Is.EqualTo(1)); + // stop shouldn't + Assert.That(compStop.called, Is.EqualTo(0)); + + // unspawn should reset netid and remove authority + NetworkServer.UnSpawn(go); + Assert.That(identity.netId, Is.Zero); + + // should be changed + Assert.That(identity.hasAuthority, Is.False); + // same as before + Assert.That(compStart.called, Is.EqualTo(1)); + // stop should be called + Assert.That(compStop.called, Is.EqualTo(1)); + } + + // test to reproduce a bug where stopping the server would not call + // OnStopServer on scene objects: + // https://github.com/vis2k/Mirror/issues/2119 + [Test] + public void Shutdown_CallsSceneObjectsOnStopServer() + { + // listen & connect a client + NetworkServer.Listen(1); + ConnectClientBlocking(out NetworkConnectionToClient _); + + // create & spawn an object + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity identity, + out StopServerCalledNetworkBehaviour comp); + + // make sure it was spawned as a scene object. + // they don't come from prefabs, so they always are. + Assert.That(identity.sceneId, !Is.Null); + + // shutdown should call OnStopServer etc. + NetworkServer.Shutdown(); + Assert.That(comp.called, Is.EqualTo(1)); + } + + [Test] + public void ShutdownCleanup() + { + // listen + NetworkServer.Listen(1); + + // add some test event hooks to make sure they are cleaned up. + // there used to be a bug where they wouldn't be cleaned up. + NetworkServer.OnConnectedEvent = connection => {}; + NetworkServer.OnDisconnectedEvent = connection => {}; + + // set local connection + NetworkServer.SetLocalConnection(new LocalConnectionToClient()); + + // connect a client + transport.ClientConnect("localhost"); + UpdateTransport(); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + + // shutdown + NetworkServer.Shutdown(); + + // state cleared? + Assert.That(NetworkServer.dontListen, Is.False); + Assert.That(NetworkServer.active, Is.False); + Assert.That(NetworkServer.isLoadingScene, Is.False); + + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + Assert.That(NetworkServer.connectionsCopy.Count, Is.EqualTo(0)); + Assert.That(NetworkServer.handlers.Count, Is.EqualTo(0)); + Assert.That(NetworkServer.newObservers.Count, Is.EqualTo(0)); + Assert.That(NetworkServer.spawned.Count, Is.EqualTo(0)); + + Assert.That(NetworkServer.localConnection, Is.Null); + Assert.That(NetworkServer.localClientActive, Is.False); + + Assert.That(NetworkServer.OnConnectedEvent, Is.Null); + Assert.That(NetworkServer.OnDisconnectedEvent, Is.Null); + Assert.That(NetworkServer.OnErrorEvent, Is.Null); + } + + [Test] + public void SendToAll_CalledWhileNotActive_ShouldGiveWarning() + { + LogAssert.Expect(LogType.Warning, $"Can not send using NetworkServer.SendToAll(T msg) because NetworkServer is not active"); + NetworkServer.SendToAll(new NetworkPingMessage {}); + } + + [Test] + public void SendToReady_CalledWhileNotActive_ShouldGiveWarning() + { + LogAssert.Expect(LogType.Warning, $"Can not send using NetworkServer.SendToReady(T msg) because NetworkServer is not active"); + NetworkServer.SendToReady(new NetworkPingMessage {}); + } + + [Test] + public void NoExternalConnections_WithNoConnection() + { + Assert.That(NetworkServer.connections.Count, Is.EqualTo(0)); + Assert.That(NetworkServer.NoExternalConnections(), Is.True); + } + + [Test] + public void NoExternalConnections_WithConnections() + { + NetworkServer.connections.Add(1, null); + NetworkServer.connections.Add(2, null); + Assert.That(NetworkServer.NoExternalConnections(), Is.False); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(2)); + } + + [Test] + public void NoExternalConnections_WithHostOnly() + { + CreateLocalConnectionPair(out LocalConnectionToClient connectionToClient, out _); + + NetworkServer.SetLocalConnection(connectionToClient); + NetworkServer.connections.Add(0, connectionToClient); + + Assert.That(NetworkServer.NoExternalConnections(), Is.True); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(1)); + + NetworkServer.RemoveLocalConnection(); + } + + [Test] + public void NoExternalConnections_WithHostAndConnection() + { + CreateLocalConnectionPair(out LocalConnectionToClient connectionToClient, out _); + + NetworkServer.SetLocalConnection(connectionToClient); + NetworkServer.connections.Add(0, connectionToClient); + NetworkServer.connections.Add(1, null); + + Assert.That(NetworkServer.NoExternalConnections(), Is.False); + Assert.That(NetworkServer.connections.Count, Is.EqualTo(2)); + + NetworkServer.RemoveLocalConnection(); + } + + // updating NetworkServer with a null entry in connection.observing + // should log a warning. someone probably used GameObject.Destroy + // instead of NetworkServer.Destroy. + [Test] + public void UpdateDetectsNullEntryInObserving() + { + // start + NetworkServer.Listen(1); + + // add a connection that is observed by a null entity + NetworkServer.connections[42] = new FakeNetworkConnection{isReady=true}; + NetworkServer.connections[42].observing.Add(null); + + // update + LogAssert.Expect(LogType.Warning, new Regex("Found 'null' entry in observing list.*")); + NetworkServer.NetworkLateUpdate(); + } + + // updating NetworkServer with a null entry in connection.observing + // should log a warning. someone probably used GameObject.Destroy + // instead of NetworkServer.Destroy. + // + // => need extra test because of Unity's custom null check + [Test] + public void UpdateDetectsDestroyedEntryInObserving() + { + // start + NetworkServer.Listen(1); + + // add a connection that is observed by a destroyed entity + CreateNetworked(out GameObject go, out NetworkIdentity ni); + NetworkServer.connections[42] = new FakeNetworkConnection{isReady=true}; + NetworkServer.connections[42].observing.Add(ni); + GameObject.DestroyImmediate(go); + + // update + LogAssert.Expect(LogType.Warning, new Regex("Found 'null' entry in observing list.*")); + NetworkServer.NetworkLateUpdate(); + } + + // SyncLists/Dict/Set .changes are only flushed when serializing. + // if an object has no observers, then serialize is never called. + // if we still keep changing the lists, then .changes would grow forever. + // => need to make sure that .changes doesn't grow while no observers. + [Test] + public void SyncObjectChanges_DontGrowWithoutObservers() + { + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // one monster + CreateNetworkedAndSpawn(out _, out NetworkIdentity identity, out NetworkBehaviourWithSyncVarsAndCollections comp); + + // without AOI, connections observer everything. + // clear the observers first. + identity.ClearObservers(); + + // insert into a synclist, which would add to .changes + comp.list.Add(42); + + // update everything once + ProcessMessages(); + + // changes should be empty since we have no observers + Assert.That(comp.list.GetChangeCount(), Is.EqualTo(0)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkServerTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs.meta new file mode 100644 index 000000000..35d033b31 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkServerTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a47dc0cc3af7c49a18a4a1b8d7de9caa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs b/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs new file mode 100644 index 000000000..748ae3fb9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs @@ -0,0 +1,362 @@ +// TODO add true over-the-network movement tests. +// but we need to split NetworkIdentity.spawned in server/client first. +// atm we can't spawn an object on both server & client separately yet. +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.NetworkTransform2k +{ + // helper class to expose some of the protected methods + public class NetworkTransformExposed : NetworkTransform + { + public new NTSnapshot ConstructSnapshot() => base.ConstructSnapshot(); + public new void ApplySnapshot(NTSnapshot start, NTSnapshot goal, NTSnapshot interpolated) => + base.ApplySnapshot(start, goal, interpolated); + public new void OnClientToServerSync(Vector3? position, Quaternion? rotation, Vector3? scale) => + base.OnClientToServerSync(position, rotation, scale); + public new void OnServerToClientSync(Vector3? position, Quaternion? rotation, Vector3? scale) => + base.OnServerToClientSync(position, rotation, scale); + } + + public class NetworkTransform2kTests : MirrorTest + { + // networked and spawned NetworkTransform + NetworkConnectionToClient connectionToClient; + Transform transform; + NetworkTransformExposed component; + + [SetUp] + public override void SetUp() + { + // set up world with server & client + // host mode for now. + // TODO separate client & server after .spawned split. + // we can use CreateNetworkedAndSpawn that creates on sv & cl. + // then move on server, update, verify client position etc. + base.SetUp(); + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + connectionToClient = NetworkServer.localConnection; + + // create a networked object with NetworkTransform + CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity _, out component, connectionToClient); + // sync immediately + component.syncInterval = 0; + // remember transform for convenience + transform = go.transform; + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + NetworkClient.Disconnect(); + } + + // TODO move to NTSnapshot tests? + [Test] + public void Interpolate() + { + NTSnapshot from = new NTSnapshot( + 1, + 1, + new Vector3(1, 1, 1), + Quaternion.Euler(new Vector3(0, 0, 0)), + new Vector3(3, 3, 3) + ); + + NTSnapshot to = new NTSnapshot( + 2, + 2, + new Vector3(2, 2, 2), + Quaternion.Euler(new Vector3(0, 90, 0)), + new Vector3(4, 4, 4) + ); + + // interpolate + NTSnapshot between = NTSnapshot.Interpolate(from, to, 0.5); + + // note: timestamp interpolation isn't needed. we don't use it. + //Assert.That(between.remoteTimestamp, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + //Assert.That(between.localTimestamp, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + + // check position + Assert.That(between.position.x, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + Assert.That(between.position.y, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + Assert.That(between.position.z, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + + // check rotation + // (epsilon is slightly too small) + Assert.That(between.rotation.eulerAngles.x, Is.EqualTo(0).Within(Mathf.Epsilon)); + Assert.That(between.rotation.eulerAngles.y, Is.EqualTo(45).Within(0.001)); + Assert.That(between.rotation.eulerAngles.z, Is.EqualTo(0).Within(Mathf.Epsilon)); + + // check scale + Assert.That(between.scale.x, Is.EqualTo(3.5).Within(Mathf.Epsilon)); + Assert.That(between.scale.y, Is.EqualTo(3.5).Within(Mathf.Epsilon)); + Assert.That(between.scale.z, Is.EqualTo(3.5).Within(Mathf.Epsilon)); + } + + [Test] + public void ConstructSnapshot() + { + // set unique position/rotation/scale + transform.position = new Vector3(1, 2, 3); + transform.rotation = Quaternion.identity; + transform.localScale = new Vector3(4, 5, 6); + + // construct snapshot + double time = NetworkTime.localTime; + NTSnapshot snapshot = component.ConstructSnapshot(); + Assert.That(snapshot.remoteTimestamp, Is.EqualTo(time).Within(0.01)); + Assert.That(snapshot.position, Is.EqualTo(new Vector3(1, 2, 3))); + Assert.That(snapshot.rotation, Is.EqualTo(Quaternion.identity)); + Assert.That(snapshot.scale, Is.EqualTo(new Vector3(4, 5, 6))); + } + + [Test] + public void ApplySnapshot_Interpolated() + { + // construct snapshot with unique position/rotation/scale + Vector3 position = new Vector3(1, 2, 3); + Quaternion rotation = Quaternion.Euler(45, 90, 45); + Vector3 scale = new Vector3(4, 5, 6); + + // apply snapshot with interpolation + component.syncPosition = true; + component.syncRotation = true; + component.syncScale = true; + component.interpolatePosition = true; + component.interpolateRotation = true; + component.interpolateScale = true; + component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + + // was it applied? + Assert.That(transform.position, Is.EqualTo(position)); + Assert.That(Quaternion.Angle(transform.rotation, rotation), Is.EqualTo(0).Within(Mathf.Epsilon)); + Assert.That(transform.localScale, Is.EqualTo(scale)); + } + + [Test] + public void ApplySnapshot_Direct() + { + // construct snapshot with unique position/rotation/scale + Vector3 position = new Vector3(1, 2, 3); + Quaternion rotation = Quaternion.Euler(45, 90, 45); + Vector3 scale = new Vector3(4, 5, 6); + + // apply snapshot without interpolation + component.syncPosition = true; + component.syncRotation = true; + component.syncScale = true; + component.interpolatePosition = false; + component.interpolateRotation = false; + component.interpolateScale = false; + component.ApplySnapshot(default, new NTSnapshot(0, 0, position, rotation, scale), default); + + // was it applied? + Assert.That(transform.position, Is.EqualTo(position)); + Assert.That(Quaternion.Angle(transform.rotation, rotation), Is.EqualTo(0).Within(Mathf.Epsilon)); + Assert.That(transform.localScale, Is.EqualTo(scale)); + } + + [Test] + public void ApplySnapshot_DontSyncPosition() + { + // construct snapshot with unique position/rotation/scale + Vector3 position = new Vector3(1, 2, 3); + Quaternion rotation = Quaternion.Euler(45, 90, 45); + Vector3 scale = new Vector3(4, 5, 6); + + // apply snapshot without position sync should not apply position + component.syncPosition = false; + component.syncRotation = true; + component.syncScale = true; + component.interpolatePosition = false; + component.interpolateRotation = true; + component.interpolateScale = true; + component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + + // was it applied? + Assert.That(transform.position, Is.EqualTo(Vector3.zero)); + Assert.That(Quaternion.Angle(transform.rotation, rotation), Is.EqualTo(0).Within(Mathf.Epsilon)); + Assert.That(transform.localScale, Is.EqualTo(scale)); + } + + [Test] + public void ApplySnapshot_DontSyncRotation() + { + // construct snapshot with unique position/rotation/scale + Vector3 position = new Vector3(1, 2, 3); + Quaternion rotation = Quaternion.Euler(45, 90, 45); + Vector3 scale = new Vector3(4, 5, 6); + + // apply snapshot without position sync should not apply position + component.syncPosition = true; + component.syncRotation = false; + component.syncScale = true; + component.interpolatePosition = true; + component.interpolateRotation = false; + component.interpolateScale = true; + component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + + // was it applied? + Assert.That(transform.position, Is.EqualTo(position)); + Assert.That(transform.rotation, Is.EqualTo(Quaternion.identity)); + Assert.That(transform.localScale, Is.EqualTo(scale)); + } + + [Test] + public void ApplySnapshot_DontSyncScale() + { + // construct snapshot with unique position/rotation/scale + Vector3 position = new Vector3(1, 2, 3); + Quaternion rotation = Quaternion.Euler(45, 90, 45); + Vector3 scale = new Vector3(4, 5, 6); + + // apply snapshot without position sync should not apply position + component.syncPosition = true; + component.syncRotation = true; + component.syncScale = false; + component.interpolatePosition = true; + component.interpolateRotation = true; + component.interpolateScale = false; + component.ApplySnapshot(default, default, new NTSnapshot(0, 0, position, rotation, scale)); + + // was it applied? + Assert.That(transform.position, Is.EqualTo(position)); + Assert.That(Quaternion.Angle(transform.rotation, rotation), Is.EqualTo(0).Within(Mathf.Epsilon)); + Assert.That(transform.localScale, Is.EqualTo(Vector3.one)); + } + + [Test] + public void OnClientToServerSync_WithoutClientAuthority() + { + // call OnClientToServerSync without authority + component.clientAuthority = false; + component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.serverBuffer.Count, Is.EqualTo(0)); + } + + [Test] + public void OnClientToServerSync_WithClientAuthority() + { + // call OnClientToServerSync with authority + component.clientAuthority = true; + component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + } + + [Test] + public void OnClientToServerSync_WithClientAuthority_BufferSizeLimit() + { + component.bufferSizeLimit = 1; + + // authority is required + component.clientAuthority = true; + + // add first should work + component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + + // add second should be too much + component.OnClientToServerSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + } + + [Test] + public void OnClientToServerSync_WithClientAuthority_Nullables_Uses_Last() + { + // set some defaults + transform.position = Vector3.left; + transform.rotation = Quaternion.identity; + transform.localScale = Vector3.right; + + // call OnClientToServerSync with authority and nullable types + // to make sure it uses the last valid position then. + component.clientAuthority = true; + component.OnClientToServerSync(new Vector3?(), new Quaternion?(), new Vector3?()); + Assert.That(component.serverBuffer.Count, Is.EqualTo(1)); + NTSnapshot first = component.serverBuffer.Values[0]; + Assert.That(first.position, Is.EqualTo(Vector3.left)); + Assert.That(first.rotation, Is.EqualTo(Quaternion.identity)); + Assert.That(first.scale, Is.EqualTo(Vector3.right)); + } + + // server->client sync should only work if client doesn't have authority + [Test] + public void OnServerToClientSync_WithoutClientAuthority() + { + // pretend to be the client object + component.netIdentity.isServer = false; + component.netIdentity.isClient = true; + component.netIdentity.isLocalPlayer = true; + + // call OnServerToClientSync without authority + component.clientAuthority = false; + component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + } + + // server->client sync shouldn't work if client has authority + [Test] + public void OnServerToClientSync_WithoutClientAuthority_bufferSizeLimit() + { + component.bufferSizeLimit = 1; + + // pretend to be the client object + component.netIdentity.isServer = false; + component.netIdentity.isClient = true; + component.netIdentity.isLocalPlayer = true; + + // client authority has to be disabled + component.clientAuthority = false; + + // add first should work + component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + + // add second should be too much + component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + } + + // server->client sync shouldn't work if client has authority + [Test] + public void OnServerToClientSync_WithClientAuthority() + { + // pretend to be the client object + component.netIdentity.isServer = false; + component.netIdentity.isClient = true; + component.netIdentity.isLocalPlayer = true; + + // call OnServerToClientSync with authority + component.clientAuthority = true; + component.OnServerToClientSync(Vector3.zero, Quaternion.identity, Vector3.zero); + Assert.That(component.clientBuffer.Count, Is.EqualTo(0)); + } + + [Test] + public void OnServerToClientSync_WithClientAuthority_Nullables_Uses_Last() + { + // set some defaults + transform.position = Vector3.left; + transform.rotation = Quaternion.identity; + transform.localScale = Vector3.right; + + // pretend to be the client object + component.netIdentity.isServer = false; + component.netIdentity.isClient = true; + component.netIdentity.isLocalPlayer = true; + + // call OnClientToServerSync with authority and nullable types + // to make sure it uses the last valid position then. + component.OnServerToClientSync(new Vector3?(), new Quaternion?(), new Vector3?()); + Assert.That(component.clientBuffer.Count, Is.EqualTo(1)); + NTSnapshot first = component.clientBuffer.Values[0]; + Assert.That(first.position, Is.EqualTo(Vector3.left)); + Assert.That(first.rotation, Is.EqualTo(Quaternion.identity)); + Assert.That(first.scale, Is.EqualTo(Vector3.right)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs.meta b/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs.meta new file mode 100644 index 000000000..89de25521 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkTransform2kTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 757a5c351d7046538812958112ce74f3 +timeCreated: 1624953125 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs new file mode 100644 index 000000000..93d6ce211 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs @@ -0,0 +1,67 @@ +using System; +using Mirror.Tests.RemoteAttrributeTest; +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class NetworkWriterCollectionTest + { + [Test] + public void HasWriteFunctionForInt() + { + Assert.That(Writer.write, Is.Not.Null, "int write function was not found"); + + Action action = NetworkWriterExtensions.WriteInt; + Assert.That(Writer.write, Is.EqualTo(action), "int write function was incorrect value"); + } + + [Test] + public void HasReadFunctionForInt() + { + Assert.That(Reader.read, Is.Not.Null, "int read function was not found"); + + Func action = NetworkReaderExtensions.ReadInt; + Assert.That(Reader.read, Is.EqualTo(action), "int read function was incorrect value"); + } + + [Test] + public void HasWriteNetworkBehaviourFunction() + { + Assert.That(Writer.write, Is.Not.Null, "NetworkBehaviour read function was not found"); + + Action action = NetworkWriterExtensions.WriteNetworkBehaviour; + Assert.That(Writer.write, Is.EqualTo(action), "NetworkBehaviour read function was incorrect value"); + } + + [Test] + public void HasReadNetworkBehaviourFunction() + { + Assert.That(Reader.read, Is.Not.Null, "NetworkBehaviour read function was not found"); + + Func actionNonGeneric = NetworkReaderExtensions.ReadNetworkBehaviour; + Func actionGeneric = NetworkReaderExtensions.ReadNetworkBehaviour; + Assert.That(Reader.read, Is.EqualTo(actionNonGeneric).Or.EqualTo(actionGeneric), + "NetworkBehaviour read function was incorrect value, should be generic or non-generic"); + } + + [Test] + public void HasWriteNetworkBehaviourDerivedFunction() + { + // needs a networkbehaviour that is included in an Message/Rpc/syncvar for this test + Assert.That(Writer.write, Is.Not.Null, "RpcNetworkIdentityBehaviour read function was not found"); + + Action action = NetworkWriterExtensions.WriteNetworkBehaviour; + Assert.That(Writer.write, Is.EqualTo(action), "RpcNetworkIdentityBehaviour read function was incorrect value"); + } + + [Test] + public void HasReadNetworkBehaviourDerivedFunction() + { + Func reader = Reader.read; + Assert.That(reader, Is.Not.Null, "RpcNetworkIdentityBehaviour read function was not found"); + + Func action = NetworkReaderExtensions.ReadNetworkBehaviour; + Assert.That(reader, Is.EqualTo(action), "RpcNetworkIdentityBehaviour read function was incorrect value"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs.meta new file mode 100644 index 000000000..2dffba4eb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkWriterCollectionTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfc7cd9211e6145418289e9c9572ce55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs new file mode 100644 index 000000000..b40211e6e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs @@ -0,0 +1,1389 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Mirror.Tests.RemoteAttrributeTest; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + [TestFixture] + public class NetworkWriterTest : MirrorEditModeTest + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // start server & connect client because we need spawn functions + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + + /* uncomment if needed. commented for faster test workflow. this takes >3s. + [Test] + public void Benchmark() + { + // 10 million reads, Unity 2019.3, code coverage disabled + // 4014ms ms + NetworkWriter writer = new NetworkWriter(); + for (int i = 0; i < 10000000; ++i) + { + writer.SetLength(0); + writer.WriteVector3(new Vector3(1, 2, 3)); + } + } + */ + + // Write/ReadBlittable assumes same endianness on server & client. + [Test] + public void LittleEndianPlatform() + { + Assert.That(BitConverter.IsLittleEndian, Is.True); + } + + // some platforms may not support unaligned *(T*) reads/writes. + // but it still needs to work with our workaround. + // let's have an editor test to maybe catch it early. + // Editor runs Win/Mac/Linux and atm the issue only exists on Android, + // but let's have a test anyway. + // see also: https://github.com/vis2k/Mirror/issues/3044 + [Test] + public void WriteUnaligned() + { + NetworkWriter writer = new NetworkWriter(); + // make unaligned + writer.WriteByte(0xFF); + // write a double + writer.WriteDouble(Math.PI); + // should have written 9 bytes without throwing exceptions + Assert.That(writer.Position, Is.EqualTo(9)); + } + + [Test] + public void TestWritingSmallMessage() + { + // try serializing less than 32kb and see what happens + NetworkWriter writer = new NetworkWriter(); + for (int i = 0; i < 30000 / 4; ++i) + writer.WriteInt(i); + Assert.That(writer.Position, Is.EqualTo(30000)); + } + + [Test] + public void TestWritingLargeMessage() + { + // try serializing more than 32kb and see what happens + NetworkWriter writer = new NetworkWriter(); + for (int i = 0; i < 40000 / 4; ++i) + writer.WriteInt(i); + Assert.That(writer.Position, Is.EqualTo(40000)); + } + + [Test] + public void TestWritingHugeArray() + { + // try serializing array more than 64KB large and see what happens + NetworkWriter writer = new NetworkWriter(); + writer.WriteBytesAndSize(new byte[100000]); + byte[] data = writer.ToArray(); + + NetworkReader reader = new NetworkReader(data); + byte[] deserialized = reader.ReadBytesAndSize(); + Assert.That(deserialized.Length, Is.EqualTo(100000)); + } + + [Test] + public void TestWritingBytesSegment() + { + byte[] data = { 1, 2, 3 }; + NetworkWriter writer = new NetworkWriter(); + writer.WriteBytes(data, 0, data.Length); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + ArraySegment deserialized = reader.ReadBytesSegment(data.Length); + Assert.That(deserialized.Count, Is.EqualTo(data.Length)); + for (int i = 0; i < data.Length; ++i) + Assert.That(deserialized.Array[deserialized.Offset + i], Is.EqualTo(data[i])); + } + + // write byte[], read segment + [Test] + public void TestWritingBytesAndReadingSegment() + { + byte[] data = { 1, 2, 3 }; + NetworkWriter writer = new NetworkWriter(); + writer.WriteBytesAndSize(data); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + ArraySegment deserialized = reader.ReadBytesAndSizeSegment(); + Assert.That(deserialized.Count, Is.EqualTo(data.Length)); + for (int i = 0; i < data.Length; ++i) + Assert.That(deserialized.Array[deserialized.Offset + i], Is.EqualTo(data[i])); + } + + // write segment, read segment + [Test] + public void TestWritingSegmentAndReadingSegment() + { + byte[] data = { 1, 2, 3, 4 }; + // [2, 3] + ArraySegment segment = new ArraySegment(data, 1, 1); + NetworkWriter writer = new NetworkWriter(); + writer.WriteBytesAndSizeSegment(segment); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + ArraySegment deserialized = reader.ReadBytesAndSizeSegment(); + Assert.That(deserialized.Count, Is.EqualTo(segment.Count)); + for (int i = 0; i < segment.Count; ++i) + Assert.That(deserialized.Array[deserialized.Offset + i], Is.EqualTo(segment.Array[segment.Offset + i])); + } + + [Test] + public void TestResetSetsPotionAndLength() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteString("I saw"); + writer.WriteLong(0xA_FADED_DEAD_EEL); + writer.WriteString("and ate it"); + writer.Reset(); + + Assert.That(writer.Position, Is.EqualTo(0)); + + byte[] data = writer.ToArray(); + Assert.That(data, Is.Empty); + } + + [Test] + public void TestReading0LengthBytesAndSize() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteBytesAndSize(new byte[] {}); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.That(reader.ReadBytesAndSize().Length, Is.EqualTo(0)); + } + + [Test] + public void TestReading0LengthBytes() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteBytes(new byte[] {}, 0, 0); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.That(reader.ReadBytes(0).Length, Is.EqualTo(0)); + } + + [Test] + public void TestWritingNegativeBytesAndSizeFailure() + { + NetworkWriter writer = new NetworkWriter(); + Assert.Throws(() => writer.WriteBytesAndSize(new byte[0], 0, -1)); + Assert.That(writer.Position, Is.EqualTo(0)); + } + + [Test] + public void TestReadingTooMuch() + { + void EnsureThrows(Action read, byte[] data = null) + { + Assert.Throws(() => read(new NetworkReader(data ?? new byte[] {}))); + } + // Try reading more than there is data to be read from + // This should throw EndOfStreamException always + EnsureThrows(r => r.ReadByte()); + EnsureThrows(r => r.ReadSByte()); + EnsureThrows(r => r.ReadChar()); + EnsureThrows(r => r.ReadBool()); + EnsureThrows(r => r.ReadShort()); + EnsureThrows(r => r.ReadUShort()); + EnsureThrows(r => r.ReadInt()); + EnsureThrows(r => r.ReadUInt()); + EnsureThrows(r => r.ReadLong()); + EnsureThrows(r => r.ReadULong()); + EnsureThrows(r => r.ReadDecimal()); + EnsureThrows(r => r.ReadFloat()); + EnsureThrows(r => r.ReadDouble()); + EnsureThrows(r => r.ReadString()); + EnsureThrows(r => r.ReadBytes(1)); + EnsureThrows(r => r.ReadBytes(2)); + EnsureThrows(r => r.ReadBytes(3)); + EnsureThrows(r => r.ReadBytes(4)); + EnsureThrows(r => r.ReadBytes(8)); + EnsureThrows(r => r.ReadBytes(16)); + EnsureThrows(r => r.ReadBytes(32)); + EnsureThrows(r => r.ReadBytes(100)); + EnsureThrows(r => r.ReadBytes(1000)); + EnsureThrows(r => r.ReadBytes(10000)); + EnsureThrows(r => r.ReadBytes(1000000)); + EnsureThrows(r => r.ReadBytes(10000000)); + EnsureThrows(r => r.ReadBytesAndSize()); + EnsureThrows(r => r.ReadVector2()); + EnsureThrows(r => r.ReadVector3()); + EnsureThrows(r => r.ReadVector4()); + EnsureThrows(r => r.ReadVector2Int()); + EnsureThrows(r => r.ReadVector3Int()); + EnsureThrows(r => r.ReadColor()); + EnsureThrows(r => r.ReadColor32()); + EnsureThrows(r => r.ReadQuaternion()); + EnsureThrows(r => r.ReadRect()); + EnsureThrows(r => r.ReadPlane()); + EnsureThrows(r => r.ReadRay()); + EnsureThrows(r => r.ReadMatrix4x4()); + EnsureThrows(r => r.ReadGuid()); + } + + [Test] + public void TestBool() + { + bool[] inputs = { true, false }; + foreach (bool input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteBool(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + bool output = reader.ReadBool(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestBoolNullable() + { + bool? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteBoolNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + bool? output = reader.ReadBoolNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestByte() + { + byte[] inputs = { 1, 2, 3, 4 }; + foreach (byte input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteByte(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + byte output = reader.ReadByte(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestByteNullable() + { + byte? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteByteNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + byte? output = reader.ReadByteNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestSByte() + { + sbyte[] inputs = { 1, 2, 3, 4 }; + foreach (sbyte input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteSByte(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + sbyte output = reader.ReadSByte(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestSByteNullable() + { + sbyte? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteSByteNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + sbyte? output = reader.ReadSByteNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestVector2() + { + Vector2[] inputs = { + Vector2.right, + Vector2.up, + Vector2.zero, + Vector2.one, + Vector2.positiveInfinity, + new Vector2(0.1f,3.1f) + }; + foreach (Vector2 input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector2(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector2 output = reader.ReadVector2(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestVector2Nullable() + { + Vector2? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector2Nullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector2? output = reader.ReadVector2Nullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestVector3() + { + Vector3[] inputs = { + Vector3.right, + Vector3.up, + Vector3.zero, + Vector3.one, + Vector3.positiveInfinity, + Vector3.forward, + new Vector3(0.1f,3.1f,1.4f) + }; + foreach (Vector3 input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector3(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector3 output = reader.ReadVector3(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestVector3Nullable() + { + Vector3? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector3Nullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector3? output = reader.ReadVector3Nullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestVector4() + { + Vector4[] inputs = { + Vector3.right, + Vector3.up, + Vector4.zero, + Vector4.one, + Vector4.positiveInfinity, + new Vector4(0.1f,3.1f,1.4f,4.9f) + }; + foreach (Vector4 input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector4(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector4 output = reader.ReadVector4(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestVector4Nullable() + { + Vector4? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector4Nullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector4? output = reader.ReadVector4Nullable(); + Assert.That(output, Is.EqualTo(output)); + } + + [Test] + public void TestVector2Int() + { + Vector2Int[] inputs = { + Vector2Int.down, + Vector2Int.up, + Vector2Int.left, + Vector2Int.zero, + new Vector2Int(-1023,-999999), + new Vector2Int(257,12345), + new Vector2Int(0x7fffffff,-12345), + }; + foreach (Vector2Int input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector2Int(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector2Int output = reader.ReadVector2Int(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestVector2IntNullable() + { + Vector2Int? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector2IntNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector2Int? output = reader.ReadVector2IntNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestVector3Int() + { + Vector3Int[] inputs = { + Vector3Int.down, + Vector3Int.up, + Vector3Int.left, + Vector3Int.one, + Vector3Int.zero, + new Vector3Int(-1023,-999999,1392), + new Vector3Int(257,12345,-6132), + new Vector3Int(0x7fffffff,-12345,-1), + }; + foreach (Vector3Int input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector3Int(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector3Int output = reader.ReadVector3Int(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestVector3IntNullable() + { + Vector3Int? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteVector3IntNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Vector3Int? output = reader.ReadVector3IntNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestColor() + { + Color[] inputs = { + Color.black, + Color.blue, + Color.cyan, + Color.yellow, + Color.magenta, + Color.white, + new Color(0.401f,0.2f,1.0f,0.123f) + }; + foreach (Color input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteColor(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Color output = reader.ReadColor(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestColorNullable() + { + Color? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteColorNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Color? output = reader.ReadColorNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestColor32() + { + Color32[] inputs = { + Color.black, + Color.blue, + Color.cyan, + Color.yellow, + Color.magenta, + Color.white, + new Color32(0xab,0xcd,0xef,0x12), + new Color32(125,126,0,255) + }; + foreach (Color32 input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteColor32(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Color32 output = reader.ReadColor32(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestColor32Nullable() + { + Color32? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteColor32Nullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Color32? output = reader.ReadColor32Nullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestQuaternion() + { + Quaternion[] inputs = { + Quaternion.identity, + default, + Quaternion.LookRotation(new Vector3(0.3f,0.4f,0.5f)), + Quaternion.Euler(45f,56f,Mathf.PI) + }; + foreach (Quaternion input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteQuaternion(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Quaternion output = reader.ReadQuaternion(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestQuaternionNullable() + { + Quaternion? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteQuaternionNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Quaternion? output = reader.ReadQuaternionNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestRect() + { + Rect[] inputs = { + Rect.zero, + new Rect(1004.1f,2.001f,4636,400f), + new Rect(-100.622f,-200f,300f,975.6f), + new Rect(-100f,435,-30.04f,400f), + new Rect(55,-200f,-44,-123), + }; + foreach (Rect input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteRect(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Rect output = reader.ReadRect(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestRectNullable() + { + Rect? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteRectNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Rect? output = reader.ReadRectNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestPlane() + { + Plane[] inputs = { + new Plane(new Vector3(-0.24f,0.34f,0.2f), 120.2f), + new Plane(new Vector3(0.133f,0.34f,0.122f), -10.135f), + new Plane(new Vector3(0.133f,-0.0f,float.MaxValue), -13.3f), + new Plane(new Vector3(0.1f,-0.2f,0.3f), 14.5f), + }; + foreach (Plane input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WritePlane(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Plane output = reader.ReadPlane(); + // note: Plane constructor does math internally, resulting in + // floating point precision loss that causes exact comparison + // to fail the test. So we test that the difference is small. + Assert.That((output.normal - input.normal).magnitude, Is.LessThan(1e-6f)); + Assert.That(output.distance, Is.EqualTo(input.distance)); + } + } + + [Test] + public void TestPlaneNullable() + { + Plane? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WritePlaneNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Plane? output = reader.ReadPlaneNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestRay() + { + Ray[] inputs = { + new Ray(Vector3.up,Vector3.down), + new Ray(new Vector3(0.1f,0.2f,0.3f), new Vector3(0.4f,0.5f,0.6f)), + new Ray(new Vector3(-0.3f,0.5f,0.999f), new Vector3(1f,100.1f,20f)), + }; + foreach (Ray input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteRay(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Ray output = reader.ReadRay(); + Assert.That((output.direction - input.direction).magnitude, Is.LessThan(1e-6f)); + Assert.That(output.origin, Is.EqualTo(input.origin)); + } + } + + [Test] + public void TestRayNullable() + { + Ray? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteRayNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Ray? output = reader.ReadRayNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestMatrix4x4() + { + Matrix4x4[] inputs = { + Matrix4x4.identity, + Matrix4x4.zero, + Matrix4x4.Scale(Vector3.one * 0.12345f), + Matrix4x4.LookAt(Vector2.up,Vector3.right,Vector3.forward), + Matrix4x4.Rotate(Quaternion.LookRotation(Vector3.one)), + }; + foreach (Matrix4x4 input in inputs) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteMatrix4x4(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Matrix4x4 output = reader.ReadMatrix4x4(); + Assert.That(output, Is.EqualTo(input)); + } + } + + [Test] + public void TestMatrix4x4Nullable() + { + Matrix4x4? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteMatrix4x4Nullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Matrix4x4? output = reader.ReadMatrix4x4Nullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestReadingInvalidString() + { + // These are all bytes which never show up in valid UTF8 encodings. + // NetworkReader should gracefully handle maliciously crafted input. + byte[] invalidUTF8bytes = { + 0xC0, 0xC1, 0xF5, 0xF6, + 0xF7, 0xF8, 0xF9, 0xFA, + 0xFB, 0xFC, 0xFD, 0xFE, + 0xFF, + }; + foreach (byte invalid in invalidUTF8bytes) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteString("an uncorrupted string"); + byte[] data = writer.ToArray(); + data[10] = invalid; + NetworkReader reader = new NetworkReader(data); + Assert.Throws(() => reader.ReadString()); + } + } + + [Test] + public void TestReadingTruncatedString() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteString("a string longer than 10 bytes"); + writer.Reset(); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.Throws(() => reader.ReadString()); + } + + [Test] + public void TestToArray() + { + // write 2 bytes + NetworkWriter writer = new NetworkWriter(); + writer.WriteByte(1); + writer.WriteByte(2); + + // .ToArray() length is 2? + Assert.That(writer.ToArray().Length, Is.EqualTo(2)); + + // set position back by one + writer.Position = 1; + + // Changing the position alter the size of the data + Assert.That(writer.ToArray().Length, Is.EqualTo(1)); + } + + [Test] + public void TestToArraySegment() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteString("hello"); + writer.WriteString("world"); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadString(), Is.EqualTo("hello")); + Assert.That(reader.ReadString(), Is.EqualTo("world")); + } + + // sometimes we may serialize nothing, then call ToArraySegment. + // make sure this works even if empty. + [Test] + public void TestToArraySegment_EmptyContent() + { + NetworkWriter writer = new NetworkWriter(); + ArraySegment segment = writer.ToArraySegment(); + Assert.That(segment.Count, Is.EqualTo(0)); + } + + [Test] + public void TestChar() + { + char a = 'a'; + char u = 'ⓤ'; + + NetworkWriter writer = new NetworkWriter(); + writer.WriteChar(a); + writer.WriteChar(u); + NetworkReader reader = new NetworkReader(writer.ToArray()); + char a2 = reader.ReadChar(); + Assert.That(a2, Is.EqualTo(a)); + char u2 = reader.ReadChar(); + Assert.That(u2, Is.EqualTo(u)); + } + + [Test] + public void TestCharNullable() + { + char? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteCharNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + char? output = reader.ReadCharNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestUnicodeString() + { + string[] weirdUnicode = { + "𝔲𝔫𝔦𝔠𝔬𝔡𝔢 𝔱𝔢𝔰𝔱", + "𝖚𝖓𝖎𝖈𝖔𝖉𝖊 𝖙𝖊𝖘𝖙", + "𝐮𝐧𝐢𝐜𝐨𝐝𝐞 𝐭𝐞𝐬𝐭", + "𝘶𝘯𝘪𝘤𝘰𝘥𝘦 𝘵𝘦𝘴𝘵", + "𝙪𝙣𝙞𝙘𝙤𝙙𝙚 𝙩𝙚𝙨𝙩", + "𝚞𝚗𝚒𝚌𝚘𝚍𝚎 𝚝𝚎𝚜𝚝", + "𝓊𝓃𝒾𝒸𝑜𝒹𝑒 𝓉𝑒𝓈𝓉", + "𝓾𝓷𝓲𝓬𝓸𝓭𝓮 𝓽𝓮𝓼𝓽", + "𝕦𝕟𝕚𝕔𝕠𝕕𝕖 𝕥𝕖𝕤𝕥", + "ЦПIᄃӨDΣ ƬΣƧƬ", + "ㄩ几丨匚ㄖᗪ乇 ㄒ乇丂ㄒ", + "ひ刀ノᄃのり乇 イ乇丂イ", + "Ʉ₦ł₵ØĐɆ ₮Ɇ₴₮", + "unicode test", + "ᴜɴɪᴄᴏᴅᴇ ᴛᴇꜱᴛ", + "ʇsǝʇ ǝpoɔıun", + "ยภเς๏๔є ՇєรՇ", + "ᑘᘉᓰᑢᓍᕲᘿ ᖶᘿSᖶ", + "υɳιƈσԃҽ ƚҽʂƚ", + "ʊռɨƈօɖɛ ȶɛֆȶ", + "🆄🅽🅸🅲🅾🅳🅴 🆃🅴🆂🆃", + "ⓤⓝⓘⓒⓞⓓⓔ ⓣⓔⓢⓣ", + "̶̝̳̥͈͖̝͌̈͛̽͊̏̚͠", + // test control codes + "\r\n", "\n", "\r", "\t", + "\\", "\"", "\'", + "\u0000\u0001\u0002\u0003", + "\u0004\u0005\u0006\u0007", + "\u0008\u0009\u000A\u000B", + "\u000C\u000D\u000E\u000F", + // test invalid bytes as characters + "\u00C0\u00C1\u00F5\u00F6", + "\u00F7\u00F8\u00F9\u00FA", + "\u00FB\u00FC\u00FD\u00FE", + "\u00FF", + }; + foreach (string weird in weirdUnicode) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteString(weird); + byte[] data = writer.ToArray(); + NetworkReader reader = new NetworkReader(data); + string str = reader.ReadString(); + Assert.That(str, Is.EqualTo(weird)); + } + } + + [Test] + public void TestGuid() + { + Guid originalGuid = new Guid("0123456789abcdef9876543210fedcba"); + NetworkWriter writer = new NetworkWriter(); + writer.WriteGuid(originalGuid); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + Guid readGuid = reader.ReadGuid(); + Assert.That(readGuid, Is.EqualTo(originalGuid)); + } + + [Test] + public void TestGuidNullable() + { + Guid? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteGuidNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + Guid? output = reader.ReadGuidNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestFloats() + { + float[] weirdFloats = { + 0f, + -0f, + float.Epsilon, + -float.Epsilon, + float.MaxValue, + float.MinValue, + float.NaN, + -float.NaN, + float.PositiveInfinity, + float.NegativeInfinity, + (float) double.MaxValue, + (float) double.MinValue, + (float) decimal.MaxValue, + (float) decimal.MinValue, + (float) Math.PI, + (float) Math.E + }; + foreach (float weird in weirdFloats) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteFloat(weird); + NetworkReader reader = new NetworkReader(writer.ToArray()); + float readFloat = reader.ReadFloat(); + Assert.That(readFloat, Is.EqualTo(weird)); + } + } + + [Test] + public void TestFloatNullable() + { + float? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteFloatNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + float? output = reader.ReadFloatNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestDoubles() + { + double[] weirdDoubles = { + 0d, + -0d, + double.Epsilon, + -double.Epsilon, + double.MaxValue, + double.MinValue, + double.NaN, + -double.NaN, + double.PositiveInfinity, + double.NegativeInfinity, + float.MaxValue, + float.MinValue, + (double) decimal.MaxValue, + (double) decimal.MinValue, + Math.PI, + Math.E + }; + foreach (double weird in weirdDoubles) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteDouble(weird); + NetworkReader reader = new NetworkReader(writer.ToArray()); + double readDouble = reader.ReadDouble(); + Assert.That(readDouble, Is.EqualTo(weird)); + } + } + + [Test] + public void TestDoubleNullable() + { + double? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteDoubleNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + double? output = reader.ReadDoubleNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestDecimals() + { + decimal[] weirdDecimals = { + decimal.Zero, + -decimal.Zero, + decimal.MaxValue, + decimal.MinValue, + (decimal) Math.PI, + (decimal) Math.E + }; + foreach (decimal weird in weirdDecimals) + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteDecimal(weird); + NetworkReader reader = new NetworkReader(writer.ToArray()); + decimal readDecimal = reader.ReadDecimal(); + Assert.That(readDecimal, Is.EqualTo(weird)); + } + } + + [Test] + public void TestDecimalNullable() + { + decimal? input = null; + NetworkWriter writer = new NetworkWriter(); + writer.WriteDecimalNullable(input); + NetworkReader reader = new NetworkReader(writer.ToArray()); + decimal? output = reader.ReadDecimalNullable(); + Assert.That(output, Is.EqualTo(input)); + } + + [Test] + public void TestFloatBinaryCompatibility() + { + float[] weirdFloats = { + ((float) Math.PI) / 3.0f, + ((float) Math.E) / 3.0f + }; + byte[] expected = { + 146, 10,134, 63, + 197,245,103, 63, + }; + NetworkWriter writer = new NetworkWriter(); + foreach (float weird in weirdFloats) + { + writer.WriteFloat(weird); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestDoubleBinaryCompatibility() + { + double[] weirdDoubles = { + Math.PI / 3.0d, + Math.E / 3.0d + }; + byte[] expected = { + 101,115, 45, 56, 82,193,240, 63, + 140,116,112,185,184,254,236, 63, + }; + NetworkWriter writer = new NetworkWriter(); + foreach (double weird in weirdDoubles) + { + writer.WriteDouble(weird); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestDecimalBinaryCompatibility() + { + decimal[] weirdDecimals = { + ((decimal) Math.PI) / 3.0m, + ((decimal) Math.E) / 3.0m + }; + byte[] expected = { + 0x00, 0x00, 0x1C, 0x00, 0x12, 0x37, 0xD6, 0x21, 0xAB, 0xEA, + 0x84, 0x0A, 0x5B, 0x5E, 0xB1, 0x03, 0x00, 0x00, 0x0E, 0x00, + 0x00, 0x00, 0x00, 0x00, 0xF0, 0x6D, 0xC2, 0xA4, 0x68, 0x52, + 0x00, 0x00 + }; + NetworkWriter writer = new NetworkWriter(); + foreach (decimal weird in weirdDecimals) + { + writer.WriteDecimal(weird); + } + //Debug.Log(BitConverter.ToString(writer.ToArray())); + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestByteEndianness() + { + byte[] values = { 0x12, 0x43, 0x00, 0xff, 0xab, 0x02, 0x20 }; + byte[] expected = { 0x12, 0x43, 0x00, 0xff, 0xab, 0x02, 0x20 }; + NetworkWriter writer = new NetworkWriter(); + foreach (byte value in values) + { + writer.WriteByte(value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestUShortEndianness() + { + ushort[] values = { 0x0000, 0x1234, 0xabcd, 0xF00F, 0x0FF0, 0xbeef }; + byte[] expected = { 0x00, 0x00, 0x34, 0x12, 0xcd, 0xab, 0x0F, 0xF0, 0xF0, 0x0F, 0xef, 0xbe }; + NetworkWriter writer = new NetworkWriter(); + foreach (ushort value in values) + { + writer.WriteUShort(value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestUIntEndianness() + { + uint[] values = { 0x12345678, 0xabcdef09, 0xdeadbeef }; + byte[] expected = { 0x78, 0x56, 0x34, 0x12, 0x09, 0xef, 0xcd, 0xab, 0xef, 0xbe, 0xad, 0xde }; + NetworkWriter writer = new NetworkWriter(); + foreach (uint value in values) + { + writer.WriteUInt(value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestULongEndianness() + { + ulong[] values = { 0x0123456789abcdef, 0xdeaded_beef_c0ffee }; + byte[] expected = { 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xee, 0xff, 0xc0, 0xef, 0xbe, 0xed, 0xad, 0xde }; + NetworkWriter writer = new NetworkWriter(); + foreach (ulong value in values) + { + writer.WriteULong(value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestSbyteEndianness() + { + byte[] values = { 0x12, 0x43, 0x00, 0xff, 0xab, 0x02, 0x20 }; + byte[] expected = { 0x12, 0x43, 0x00, 0xff, 0xab, 0x02, 0x20 }; + NetworkWriter writer = new NetworkWriter(); + foreach (byte value in values) + { + writer.WriteSByte((sbyte)value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestShortEndianness() + { + ushort[] values = { 0x0000, 0x1234, 0xabcd, 0xF00F, 0x0FF0, 0xbeef }; + byte[] expected = { 0x00, 0x00, 0x34, 0x12, 0xcd, 0xab, 0x0F, 0xF0, 0xF0, 0x0F, 0xef, 0xbe }; + NetworkWriter writer = new NetworkWriter(); + foreach (ushort value in values) + { + writer.WriteShort((short)value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestIntEndianness() + { + uint[] values = { 0x12345678, 0xabcdef09, 0xdeadbeef }; + byte[] expected = { 0x78, 0x56, 0x34, 0x12, 0x09, 0xef, 0xcd, 0xab, 0xef, 0xbe, 0xad, 0xde }; + NetworkWriter writer = new NetworkWriter(); + foreach (uint value in values) + { + writer.WriteInt((int)value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestLongEndianness() + { + ulong[] values = { 0x0123456789abcdef, 0xdeaded_beef_c0ffee }; + byte[] expected = { 0xef, 0xcd, 0xab, 0x89, 0x67, 0x45, 0x23, 0x01, 0xee, 0xff, 0xc0, 0xef, 0xbe, 0xed, 0xad, 0xde }; + NetworkWriter writer = new NetworkWriter(); + foreach (ulong value in values) + { + writer.WriteLong((long)value); + } + Assert.That(writer.ToArray(), Is.EqualTo(expected)); + } + + [Test] + public void TestWritingAndReading() + { + // write all simple types once + NetworkWriter writer = new NetworkWriter(); + writer.WriteChar((char)1); + writer.WriteByte(2); + writer.WriteSByte(3); + writer.WriteBool(true); + writer.WriteShort(4); + writer.WriteUShort(5); + writer.WriteInt(6); + writer.WriteUInt(7U); + writer.WriteLong(8L); + writer.WriteULong(9UL); + writer.WriteFloat(10.0F); + writer.WriteDouble(11.0D); + writer.WriteDecimal(12); + writer.WriteString(null); + writer.WriteString(""); + writer.WriteString("13"); + // just the byte array, no size info etc. + writer.WriteBytes(new byte[] { 14, 15 }, 0, 2); + // [SyncVar] struct values can have uninitialized byte arrays, null needs to be supported + writer.WriteBytesAndSize(null); + // buffer, no-offset, count + writer.WriteBytesAndSize(new byte[] { 17, 18 }, 0, 2); + // buffer, offset, count + writer.WriteBytesAndSize(new byte[] { 19, 20, 21 }, 1, 2); + // size, buffer + writer.WriteBytesAndSize(new byte[] { 22, 23 }, 0, 2); + + // read them + NetworkReader reader = new NetworkReader(writer.ToArray()); + + Assert.That(reader.ReadChar(), Is.EqualTo(1)); + Assert.That(reader.ReadByte(), Is.EqualTo(2)); + Assert.That(reader.ReadSByte(), Is.EqualTo(3)); + Assert.That(reader.ReadBool(), Is.True); + Assert.That(reader.ReadShort(), Is.EqualTo(4)); + Assert.That(reader.ReadUShort(), Is.EqualTo(5)); + Assert.That(reader.ReadInt(), Is.EqualTo(6)); + Assert.That(reader.ReadUInt(), Is.EqualTo(7)); + Assert.That(reader.ReadLong(), Is.EqualTo(8)); + Assert.That(reader.ReadULong(), Is.EqualTo(9)); + Assert.That(reader.ReadFloat(), Is.EqualTo(10)); + Assert.That(reader.ReadDouble(), Is.EqualTo(11)); + Assert.That(reader.ReadDecimal(), Is.EqualTo(12)); + // writing null string should write null in Mirror ("" in original HLAPI) + Assert.That(reader.ReadString(), Is.Null); + Assert.That(reader.ReadString(), Is.EqualTo("")); + Assert.That(reader.ReadString(), Is.EqualTo("13")); + + Assert.That(reader.ReadBytes(2), Is.EqualTo(new byte[] { 14, 15 })); + + Assert.That(reader.ReadBytesAndSize(), Is.Null); + + Assert.That(reader.ReadBytesAndSize(), Is.EqualTo(new byte[] { 17, 18 })); + + Assert.That(reader.ReadBytesAndSize(), Is.EqualTo(new byte[] { 20, 21 })); + + Assert.That(reader.ReadBytesAndSize(), Is.EqualTo(new byte[] { 22, 23 })); + } + + [Test] + public void TestWritingUri() + { + + Uri testUri = new Uri("https://www.mirror-networking.com?somthing=other"); + + NetworkWriter writer = new NetworkWriter(); + writer.WriteUri(testUri); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.That(reader.ReadUri(), Is.EqualTo(testUri)); + } + + // URI null support test for https://github.com/vis2k/Mirror/pull/2796/ + [Test] + public void TestWritingNullUri() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUri(null); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.That(reader.ReadUri(), Is.EqualTo(null)); + } + + [Test] + public void TestList() + { + List original = new List() { 1, 2, 3, 4, 5 }; + NetworkWriter writer = new NetworkWriter(); + writer.Write(original); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + List readList = reader.Read>(); + Assert.That(readList, Is.EqualTo(original)); + } + + [Test] + public void TestNullList() + { + NetworkWriter writer = new NetworkWriter(); + writer.Write>(null); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + List readList = reader.Read>(); + Assert.That(readList, Is.Null); + } + + + const int testArraySize = 4; + [Test] + [Description("ReadArray should throw if it is trying to read more than length of segment, this is to stop allocation attacks")] + public void TestArrayDoesNotThrowWithCorrectLength() + { + NetworkWriter writer = new NetworkWriter(); + WriteGoodArray(); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.DoesNotThrow(() => + { + _ = reader.ReadArray(); + }); + + void WriteGoodArray() + { + writer.WriteInt(testArraySize); + int[] array = new int[testArraySize] { 1, 2, 3, 4 }; + for (int i = 0; i < array.Length; i++) + writer.Write(array[i]); + } + } + [Test] + [Description("ReadArray should throw if it is trying to read more than length of segment, this is to stop allocation attacks")] + [TestCase(testArraySize * sizeof(int), Description = "max allowed value to allocate array")] + [TestCase(testArraySize * 2)] + [TestCase(testArraySize + 1, Description = "min allowed to allocate")] + public void TestArrayThrowsIfLengthIsWrong(int badLength) + { + NetworkWriter writer = new NetworkWriter(); + WriteBadArray(); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + Assert.Throws(() => { + _ = reader.ReadArray(); + }); + + void WriteBadArray() + { + writer.WriteInt(badLength); + int[] array = new int[testArraySize] { 1, 2, 3, 4 }; + for (int i = 0; i < array.Length; i++) + writer.Write(array[i]); + } + } + + [Test] + [Description("ReadArray should throw if it is trying to read more than length of segment, this is to stop allocation attacks")] + [TestCase(testArraySize * sizeof(int) + 1, Description = "min read count is 1 byte, 16 array bytes are writen so 17 should throw error")] + [TestCase(20_000)] + [TestCase(int.MaxValue)] + [TestCase(int.MaxValue - 1)] + // todo add fuzzy testing to check more values + public void TestArrayThrowsIfLengthIsTooBig(int badLength) + { + NetworkWriter writer = new NetworkWriter(); + WriteBadArray(); + + NetworkReader reader = new NetworkReader(writer.ToArray()); + EndOfStreamException exception = Assert.Throws(() => + { + _ = reader.ReadArray(); + }); + Assert.That(exception, Has.Message.EqualTo($"Received array that is too large: {badLength}")); + + void WriteBadArray() + { + writer.WriteInt(badLength); + int[] array = new int[testArraySize] { 1, 2, 3, 4 }; + for (int i = 0; i < array.Length; i++) + writer.Write(array[i]); + } + } + + [Test] + public void TestNetworkBehaviour() + { + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn(out _, out _, out RpcNetworkIdentityBehaviour behaviour); + + NetworkWriter writer = new NetworkWriter(); + writer.WriteNetworkBehaviour(behaviour); + + byte[] bytes = writer.ToArray(); + + Assert.That(bytes.Length, Is.EqualTo(5), "Networkbehaviour should be 5 bytes long."); + + NetworkReader reader = new NetworkReader(bytes); + RpcNetworkIdentityBehaviour actual = reader.ReadNetworkBehaviour(); + Assert.That(actual, Is.EqualTo(behaviour), "Read should find the same behaviour as written"); + } + + [Test] + public void TestNetworkBehaviourNull() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteNetworkBehaviour(null); + + byte[] bytes = writer.ToArray(); + + Assert.That(bytes.Length, Is.EqualTo(4), "null Networkbehaviour should be 4 bytes long."); + + NetworkReader reader = new NetworkReader(bytes); + RpcNetworkIdentityBehaviour actual = reader.ReadNetworkBehaviour(); + Assert.That(actual, Is.Null, "should read null"); + + Assert.That(reader.Position, Is.EqualTo(4), "should read 4 bytes when netid is 0"); + } + + [Test] + [Description("Uses Generic read function to check weaver correctly creates it")] + public void TestNetworkBehaviourWeaverGenerated() + { + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn(out _, out _, out RpcNetworkIdentityBehaviour behaviour); + + NetworkWriter writer = new NetworkWriter(); + writer.Write(behaviour); + + byte[] bytes = writer.ToArray(); + + Assert.That(bytes.Length, Is.EqualTo(5), "Networkbehaviour should be 5 bytes long."); + + NetworkReader reader = new NetworkReader(bytes); + RpcNetworkIdentityBehaviour actual = reader.Read(); + Assert.That(actual, Is.EqualTo(behaviour), "Read should find the same behaviour as written"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs.meta b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs.meta new file mode 100644 index 000000000..394706e04 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/NetworkWriterTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9f7c59e9071cf4a64a9bd207465e3f1b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/OverloadMethodTest.cs b/Assets/Mirror/Tests/Editor/OverloadMethodTest.cs new file mode 100644 index 000000000..56313714d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/OverloadMethodTest.cs @@ -0,0 +1,53 @@ +using NUnit.Framework; + +namespace Mirror.Tests.MessageTests +{ + struct NoArgMethodMessage : NetworkMessage + { + public int someValue; + + // Weaver should ignore these methods because they have no args + public void Serialize() { /* method with no arg */ } + public void Deserialize() { /* method with no arg */ } + } + + struct TwoArgMethodMessage : NetworkMessage + { + public int someValue; + + // Weaver should ignore these methods because they have two args + public void Serialize(NetworkWriter writer, int AnotherValue) {} + public void Deserialize(NetworkReader reader, int AnotherValue) {} + } + + public class OverloadMethodTest + { + [Test] + public void MethodsWithNoArgs() + { + const int value = 10; + NoArgMethodMessage intMessage = new NoArgMethodMessage + { + someValue = value + }; + + byte[] data = MessagePackingTest.PackToByteArray(intMessage); + NoArgMethodMessage unpacked = MessagePackingTest.UnpackFromByteArray(data); + Assert.That(unpacked.someValue, Is.EqualTo(value)); + } + + [Test] + public void MethodsWithTwoArgs() + { + const int value = 10; + TwoArgMethodMessage intMessage = new TwoArgMethodMessage + { + someValue = value + }; + + byte[] data = MessagePackingTest.PackToByteArray(intMessage); + TwoArgMethodMessage unpacked = MessagePackingTest.UnpackFromByteArray(data); + Assert.That(unpacked.someValue, Is.EqualTo(value)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/OverloadMethodTest.cs.meta b/Assets/Mirror/Tests/Editor/OverloadMethodTest.cs.meta new file mode 100644 index 000000000..227a26a8b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/OverloadMethodTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bbe7affc888ce1041a8d6752b0f3f94b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/PoolTests.cs b/Assets/Mirror/Tests/Editor/PoolTests.cs new file mode 100644 index 000000000..2002c8de5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/PoolTests.cs @@ -0,0 +1,45 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class PoolTests + { + Pool pool; + + [SetUp] + public void SetUp() + { + pool = new Pool(() => "new string", 0); + } + + [TearDown] + public void TearDown() + { + pool = null; + } + + [Test] + public void TakeFromEmpty() + { + // taking from an empty pool should give us a completely new string + Assert.That(pool.Take(), Is.EqualTo("new string")); + } + + [Test] + public void ReturnAndTake() + { + // returning and then taking should get the returned one, not a + // newly generated one. + pool.Return("returned"); + Assert.That(pool.Take(), Is.EqualTo("returned")); + } + + [Test] + public void Count() + { + Assert.That(pool.Count, Is.EqualTo(0)); + pool.Return("returned"); + Assert.That(pool.Count, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/PoolTests.cs.meta b/Assets/Mirror/Tests/Editor/PoolTests.cs.meta new file mode 100644 index 000000000..73ac928ce --- /dev/null +++ b/Assets/Mirror/Tests/Editor/PoolTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 566efeb4786e4449bb70c041baf39b42 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/RemoteTestBase.cs b/Assets/Mirror/Tests/Editor/RemoteTestBase.cs new file mode 100644 index 000000000..05cb4fc36 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/RemoteTestBase.cs @@ -0,0 +1,15 @@ +using NUnit.Framework; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + public class RemoteTestBase : MirrorEditModeTest + { + [SetUp] + public void Setup() + { + // start server/client + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/RemoteTestBase.cs.meta b/Assets/Mirror/Tests/Editor/RemoteTestBase.cs.meta new file mode 100644 index 000000000..45e4de8ea --- /dev/null +++ b/Assets/Mirror/Tests/Editor/RemoteTestBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 45bb26f0ce04fb749ac83a28d7590ebc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs b/Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs new file mode 100644 index 000000000..4916b8874 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs @@ -0,0 +1,114 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class RpcNetworkIdentityBehaviour : NetworkBehaviour + { + public event Action onSendNetworkIdentityCalled; + public event Action onSendGameObjectCalled; + public event Action onSendNetworkBehaviourCalled; + public event Action onSendNetworkBehaviourDerivedCalled; + + [ClientRpc] + public void SendNetworkIdentity(NetworkIdentity value) + { + onSendNetworkIdentityCalled?.Invoke(value); + } + + [ClientRpc] + public void SendGameObject(GameObject value) + { + onSendGameObjectCalled?.Invoke(value); + } + + [ClientRpc] + public void SendNetworkBehaviour(NetworkBehaviour value) + { + onSendNetworkBehaviourCalled?.Invoke(value); + } + + [ClientRpc] + public void SendNetworkBehaviourDerived(RpcNetworkIdentityBehaviour value) + { + onSendNetworkBehaviourDerivedCalled?.Invoke(value); + } + } + + [Description("Test for sending NetworkIdentity fields (NI/GO/NB) in RPC")] + public class RpcNetworkIdentityTest : RemoteTestBase + { + [Test] + public void RpcCanSendNetworkIdentity() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcNetworkIdentityBehaviour hostBehaviour, NetworkServer.localConnection); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity expected, out RpcNetworkIdentityBehaviour _, NetworkServer.localConnection); + + int callCount = 0; + hostBehaviour.onSendNetworkIdentityCalled += actual => + { + callCount++; + Assert.That(actual, Is.EqualTo(expected)); + }; + hostBehaviour.SendNetworkIdentity(expected); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void RpcCanSendGameObject() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcNetworkIdentityBehaviour hostBehaviour, NetworkServer.localConnection); + CreateNetworkedAndSpawn(out GameObject expected, out NetworkIdentity _, out RpcNetworkIdentityBehaviour _, NetworkServer.localConnection); + + int callCount = 0; + hostBehaviour.onSendGameObjectCalled += actual => + { + callCount++; + Assert.That(actual, Is.EqualTo(expected)); + }; + hostBehaviour.SendGameObject(expected); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void RpcCanSendNetworkBehaviour() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcNetworkIdentityBehaviour hostBehaviour, NetworkServer.localConnection); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcNetworkIdentityBehaviour expected, NetworkServer.localConnection); + + int callCount = 0; + hostBehaviour.onSendNetworkBehaviourCalled += actual => + { + callCount++; + Assert.That(actual, Is.EqualTo(expected)); + }; + hostBehaviour.SendNetworkBehaviour(expected); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void RpcCanSendNetworkBehaviourDerived() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcNetworkIdentityBehaviour hostBehaviour, NetworkServer.localConnection); + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out RpcNetworkIdentityBehaviour expected, NetworkServer.localConnection); + + int callCount = 0; + hostBehaviour.onSendNetworkBehaviourDerivedCalled += actual => + { + callCount++; + Assert.That(actual, Is.EqualTo(expected)); + }; + hostBehaviour.SendNetworkBehaviourDerived(expected); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs.meta b/Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs.meta new file mode 100644 index 000000000..af93365f5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/RpcNetworkIdentityTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6cd69a1f4a3c74e4fa03b9ab816d392c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs b/Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs new file mode 100644 index 000000000..b5b83b9e8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs @@ -0,0 +1,43 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class MyScriptableObject : ScriptableObject + { + public int someData; + } + + [TestFixture] + public class ScriptableObjectWriterTest + { + + // ArraySegment is a special case, optimized for no copy and no allocation + // other types are generated by the weaver + + + public struct ScriptableObjectMessage : NetworkMessage + { + public MyScriptableObject scriptableObject; + } + + [Test] + public void TestWriteScriptableObject() + { + ScriptableObjectMessage message = new ScriptableObjectMessage + { + scriptableObject = ScriptableObject.CreateInstance() + }; + + message.scriptableObject.someData = 10; + + byte[] data = MessagePackingTest.PackToByteArray(message); + + ScriptableObjectMessage unpacked = MessagePackingTest.UnpackFromByteArray(data); + + Assert.That(unpacked.scriptableObject, Is.Not.Null); + Assert.That(unpacked.scriptableObject.someData, Is.EqualTo(10)); + } + + } +} diff --git a/Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs.meta b/Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs.meta new file mode 100644 index 000000000..03cd254bd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/ScriptableObjectWriterTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b6c10e2d494114e9190f56d13a894c82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs new file mode 100644 index 000000000..1ba07b1f9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs @@ -0,0 +1,724 @@ + +using System; +using NUnit.Framework; +using System.Collections.Generic; +using UnityEngine; + +namespace Mirror.Tests +{ + // a simple snapshot with timestamp & interpolation + struct SimpleSnapshot : Snapshot + { + public double remoteTimestamp { get; set; } + public double localTimestamp { get; set; } + public double value; + + public SimpleSnapshot(double remoteTimestamp, double localTimestamp, double value) + { + this.remoteTimestamp = remoteTimestamp; + this.localTimestamp = localTimestamp; + this.value = value; + } + + public static SimpleSnapshot Interpolate(SimpleSnapshot from, SimpleSnapshot to, double t) => + new SimpleSnapshot( + // interpolated snapshot is applied directly. don't need timestamps. + 0, 0, + // lerp unclamped in case we ever need to extrapolate. + // atm SnapshotInterpolation never does. + Mathd.LerpUnclamped(from.value, to.value, t)); + } + + public class SnapshotInterpolationTests + { + // buffer for convenience so we don't have to create it manually each time + SortedList buffer; + + [SetUp] + public void SetUp() + { + buffer = new SortedList(); + } + + [Test] + public void InsertIfNewEnough() + { + // inserting a first value should always work + SimpleSnapshot first = new SimpleSnapshot(1, 1, 0); + SnapshotInterpolation.InsertIfNewEnough(first, buffer); + Assert.That(buffer.Count, Is.EqualTo(1)); + + // insert before first should not work + SimpleSnapshot before = new SimpleSnapshot(0.5, 0.5, 0); + SnapshotInterpolation.InsertIfNewEnough(before, buffer); + Assert.That(buffer.Count, Is.EqualTo(1)); + + // insert after first should work + SimpleSnapshot second = new SimpleSnapshot(2, 2, 0); + SnapshotInterpolation.InsertIfNewEnough(second, buffer); + Assert.That(buffer.Count, Is.EqualTo(2)); + Assert.That(buffer.Values[0], Is.EqualTo(first)); + Assert.That(buffer.Values[1], Is.EqualTo(second)); + + // insert after second should work + SimpleSnapshot after = new SimpleSnapshot(2.5, 2.5, 0); + SnapshotInterpolation.InsertIfNewEnough(after, buffer); + Assert.That(buffer.Count, Is.EqualTo(3)); + Assert.That(buffer.Values[0], Is.EqualTo(first)); + Assert.That(buffer.Values[1], Is.EqualTo(second)); + Assert.That(buffer.Values[2], Is.EqualTo(after)); + } + + // the 'ACB' problem: + // if we have a snapshot A at t=0 and C at t=2, + // we start interpolating between them. + // if suddenly B at t=1 comes in unexpectely, + // we should NOT suddenly steer towards B. + // => inserting between the first two snapshot should never be allowed + // in order to avoid all kinds of edge cases. + [Test] + public void InsertIfNewEnough_ACB_Problem() + { + SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); + + // insert A and C + SnapshotInterpolation.InsertIfNewEnough(a, buffer); + SnapshotInterpolation.InsertIfNewEnough(c, buffer); + + // trying to insert B between the first two snapshots should fail + SnapshotInterpolation.InsertIfNewEnough(b, buffer); + Assert.That(buffer.Count, Is.EqualTo(2)); + Assert.That(buffer.Values[0], Is.EqualTo(a)); + Assert.That(buffer.Values[1], Is.EqualTo(c)); + } + + // the 'first is lagging' problem: + // server sends A, B. + // A is lagging behind by 2000ms for whatever reason. + // we get B first. + // B should remain the first snapshot, the lagging A should be dropped + [Test] + public void InsertIfNewEnough_FirstIsLagging_Problem() + { + SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + + // insert B. A is still delayed. + SnapshotInterpolation.InsertIfNewEnough(b, buffer); + + // now the delayed A comes in. + // timestamp is before B though. + // but it should still be dropped. + SnapshotInterpolation.InsertIfNewEnough(a, buffer); + Assert.That(buffer.Count, Is.EqualTo(1)); + Assert.That(buffer.Values[0], Is.EqualTo(b)); + } + + [Test] + public void HasAmountOlderThan_NotEnough() + { + // only add two + SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + buffer.Add(a.remoteTimestamp, a); + buffer.Add(b.remoteTimestamp, b); + + // shouldn't have more old enough than two + // because we don't have more than two + Assert.That(SnapshotInterpolation.HasAmountOlderThan(buffer, 0, 3), Is.False); + } + + [Test] + public void HasAmountOlderThan_EnoughButNotOldEnough() + { + // add three + SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); + buffer.Add(a.remoteTimestamp, a); + buffer.Add(b.remoteTimestamp, b); + buffer.Add(c.remoteTimestamp, c); + + // check at time = 1.9, where third one would not be old enough. + Assert.That(SnapshotInterpolation.HasAmountOlderThan(buffer, 1.9, 3), Is.False); + } + + [Test] + public void HasAmountOlderThan_EnoughAndOldEnough() + { + // add three + SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); + buffer.Add(a.remoteTimestamp, a); + buffer.Add(b.remoteTimestamp, b); + buffer.Add(c.remoteTimestamp, c); + + // check at time = 2.1, where third one would be old enough. + Assert.That(SnapshotInterpolation.HasAmountOlderThan(buffer, 2.1, 3), Is.True); + } + + // UDP messages might arrive twice sometimes. + // make sure InsertIfNewEnough can handle it. + [Test] + public void InsertIfNewEnough_Duplicate() + { + SimpleSnapshot a = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot b = new SimpleSnapshot(1, 1, 0); + SimpleSnapshot c = new SimpleSnapshot(2, 2, 0); + + // add two valid snapshots first. + // we can't add 'duplicates' before 3rd and 4th anyway. + SnapshotInterpolation.InsertIfNewEnough(a, buffer); + SnapshotInterpolation.InsertIfNewEnough(b, buffer); + + // insert C which is newer than B. + // then insert it again because it arrive twice. + SnapshotInterpolation.InsertIfNewEnough(c, buffer); + SnapshotInterpolation.InsertIfNewEnough(c, buffer); + + // count should still be 3. + Assert.That(buffer.Count, Is.EqualTo(3)); + } + + [Test] + public void CalculateCatchup_Empty() + { + // make sure nothing happens with buffer size = 0 + Assert.That(SnapshotInterpolation.CalculateCatchup(buffer, 0, 10), Is.EqualTo(0)); + } + + [Test] + public void CalculateCatchup_None() + { + // add one + buffer.Add(0, default); + + // catch-up starts at threshold = 1. so nothing. + Assert.That(SnapshotInterpolation.CalculateCatchup(buffer, 1, 10), Is.EqualTo(0)); + } + + [Test] + public void GetFirstSecondAndDelta() + { + // add three + SimpleSnapshot a = new SimpleSnapshot(0, 1, 0); + SimpleSnapshot b = new SimpleSnapshot(2, 3, 0); + SimpleSnapshot c = new SimpleSnapshot(10, 20, 0); + buffer.Add(a.remoteTimestamp, a); + buffer.Add(b.remoteTimestamp, b); + buffer.Add(c.remoteTimestamp, c); + + SnapshotInterpolation.GetFirstSecondAndDelta(buffer, out SimpleSnapshot first, out SimpleSnapshot second, out double delta); + Assert.That(first, Is.EqualTo(a)); + Assert.That(second, Is.EqualTo(b)); + Assert.That(delta, Is.EqualTo(b.remoteTimestamp - a.remoteTimestamp)); + } + + [Test] + public void CalculateCatchup_Multiple() + { + // add three + buffer.Add(0, default); + buffer.Add(1, default); + buffer.Add(2, default); + + // catch-up starts at threshold = 1. so two are multiplied by 10. + Assert.That(SnapshotInterpolation.CalculateCatchup(buffer, 1, 10), Is.EqualTo(20)); + } + + // first step: with empty buffer and defaults, nothing should happen + [Test] + public void Compute_Step1_DefaultDoesNothing() + { + // compute with defaults + double localTime = 0; + double deltaTime = 0; + double interpolationTime = 0; + float bufferTime = 0; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should not spit out any snapshot to apply + Assert.That(result, Is.False); + // no interpolation should have happened yet + Assert.That(interpolationTime, Is.EqualTo(0)); + // buffer should still be untouched + Assert.That(buffer.Count, Is.EqualTo(0)); + } + + // third step: compute should always wait until the first two snapshots + // are older than the time we buffer ('bufferTime') + // => test for both snapshots not old enough + [Test] + public void Compute_Step3_WaitsUntilBufferTime() + { + // add two snapshots that are barely not old enough + // (localTime - bufferTime) + // IMPORTANT: use a 'definitely old enough' remoteTime to make sure + // that compute() actually checks LOCAL, not REMOTE time! + SimpleSnapshot first = new SimpleSnapshot(0.1, 0.1, 0); + SimpleSnapshot second = new SimpleSnapshot(0.9, 1.1, 0); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + double localTime = 3; + double deltaTime = 0.5; + double interpolationTime = 0; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should not spit out any snapshot to apply + Assert.That(result, Is.False); + // no interpolation should happen yet (not old enough) + Assert.That(interpolationTime, Is.EqualTo(0)); + // buffer should be untouched + Assert.That(buffer.Count, Is.EqualTo(2)); + } + + // third step: compute should always wait until the first two snapshots + // are older than the time we buffer ('bufferTime') + // => test for only one snapshot which is old enough + [Test] + public void Compute_Step3_WaitsForSecondSnapshot() + { + // add a snapshot at t = 0 + SimpleSnapshot first = new SimpleSnapshot(0, 0, 0); + buffer.Add(first.remoteTimestamp, first); + + // compute at localTime = 2 with bufferTime = 1 + // so the threshold is anything < t=1 + double localTime = 2; + double deltaTime = 0; + double interpolationTime = 0; + float bufferTime = 1; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should not spit out any snapshot to apply + Assert.That(result, Is.False); + // no interpolation should happen yet (not enough snapshots) + Assert.That(interpolationTime, Is.EqualTo(0)); + // buffer should be untouched + Assert.That(buffer.Count, Is.EqualTo(1)); + } + + // fourth step: compute should begin if we have two old enough snapshots + [Test] + public void Compute_Step4_InterpolateWithTwoOldEnoughSnapshots() + { + // add two old enough snapshots + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); + // IMPORTANT: second snapshot delta is != 1 so we can be sure that + // interpolationTime result is actual time, not 't' ratio. + // for a delta of 1, absolute and relative values would + // return the same results. + SimpleSnapshot second = new SimpleSnapshot(2, 2, 2); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + double localTime = 4; + double deltaTime = 1.5; + double interpolationTime = 0; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started just now, from 0. + // and deltaTime is 1.5, so we should be at 1.5 now. + Assert.That(interpolationTime, Is.EqualTo(1.5)); + // buffer should be untouched, we are still interpolating between the two + Assert.That(buffer.Count, Is.EqualTo(2)); + // interpolationTime is at 1.5, so 3/4 between first & second. + // computed snapshot should be interpolated at 3/4ths. + Assert.That(computed.value, Is.EqualTo(1.75).Within(Mathf.Epsilon)); + } + + // fourth step: compute should begin if we have two old enough snapshots + // => test with 3 snapshots to make sure the third one + // isn't touched while t between [0,1] + [Test] + public void Compute_Step4_InterpolateWithThreeOldEnoughSnapshots() + { + // add three old enough snapshots. + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); + SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); + SimpleSnapshot third = new SimpleSnapshot(2, 2, 2); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + buffer.Add(third.remoteTimestamp, third); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + double localTime = 4; + double deltaTime = 0.5; + double interpolationTime = 0; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started just now, from 0. + // and deltaTime is 0.5, so we should be at 0.5 now. + Assert.That(interpolationTime, Is.EqualTo(0.5)); + // buffer should be untouched, we are still interpolating between + // the first two. third should still be there. + Assert.That(buffer.Count, Is.EqualTo(3)); + // computed snapshot should be interpolated in the middle + Assert.That(computed.value, Is.EqualTo(1.5).Within(Mathf.Epsilon)); + } + + // fourth step: simulate interpolation after a long time of no updates. + // for example, a mobile user might put the app in the + // background for a minute. + [Test] + public void Compute_Step4_InterpolateAfterLongPause() + { + // add two immediate, and one that arrives 100s later + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 0); + SimpleSnapshot second = new SimpleSnapshot(1, 1, 1); + SimpleSnapshot third = new SimpleSnapshot(101, 2, 101); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + buffer.Add(third.remoteTimestamp, third); + + // compute where we are half way between first and second, + // and now are updated 1 minute later. + double localTime = 103; // 1011+bufferTime so third snapshot is old enough + double deltaTime = 98.5; // 99s - interpolation time + double interpolationTime = 0.5; // half way between first and second + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started at 0.5, right between first & second. + // we received another snapshot at t=101. + // delta = 98.5 seconds + // => interpolationTime = 99 + // => overshoots second goal, so we move to third goal and subtract 1 + // => so we should be at 98 now + Assert.That(interpolationTime, Is.EqualTo(98)); + // we moved to the next snapshot. so only 2 should be in buffer now. + Assert.That(buffer.Count, Is.EqualTo(2)); + // delta between second and third is 100. + // interpolationTime is at 98 + // interpolationTime is relative to second.time + // => InverseLerp(1, 101, 1 + 98) = 0.98 + // which is at 98% of the value + // => Lerp(1, 101, 0.98): 101-1 is 100. 98% are 98. relative to '1' + // makes it 99. + Assert.That(computed.value, Is.EqualTo(99).Within(Mathf.Epsilon)); + } + + // fourth step: catchup should be considered if buffer gets too large + [Test] + public void Compute_Step4_InterpolateWithCatchup() + { + // add two old enough snapshots + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); + SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + + // start applying 25% catchup per excess when > 2. + int catchupThreshold = 2; + float catchupMultiplier = 0.25f; + + // two excess snapshots to make sure that multiplier is accumulated + SimpleSnapshot excess1 = new SimpleSnapshot(2, 2, 3); + SimpleSnapshot excess2 = new SimpleSnapshot(3, 3, 4); + buffer.Add(excess1.remoteTimestamp, excess1); + buffer.Add(excess2.remoteTimestamp, excess2); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + double localTime = 3; + double deltaTime = 0.5; + double interpolationTime = 0; + float bufferTime = 2; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started just now, from 0. + // and deltaTime is 0.5 + 50% catchup, so we should be at 0.75 now + Assert.That(interpolationTime, Is.EqualTo(0.75)); + // buffer should be untouched, we are still interpolating between + // the first two. + Assert.That(buffer.Count, Is.EqualTo(4)); + // computed snapshot should be interpolated in 3/4 because + // interpolationTime is at 3/4 + Assert.That(computed.value, Is.EqualTo(1.75).Within(Mathf.Epsilon)); + } + + // fifth step: interpolation time overshoots the end while waiting for + // more snapshots. + // + // IMPORTANT: we should NOT extrapolate & predict while waiting for more + // snapshots as this would introduce a whole range of issues: + // * player might be extrapolated WAY out if we wait for long + // * player might be extrapolated behind walls + // * once we receive a new snapshot, we would interpolate + // not from the last valid position, but from the + // extrapolated position. this could be ANYWHERE. the + // player might get stuck in walls, etc. + // => we are NOT doing client side prediction & rollback here + // => we are simply interpolating with known, valid positions + // + // NOTE: to reproduce the issue in a real example: + // * open mirror benchmark example + // * editor=host 1000+ monsters & deep profiling for LOW FPS + // * build=client + // * move around client + // * see it all over the place in editor because it extrapolates, + // ends up at the wrong start positions and gets worse from + // there. + // + // video: https://gyazo.com/8de68f0a821449d7b9a8424e2c9e3ff8 + // (or see Mirror/Docs/Screenshots/NT Snap. Interp./extrapolation issues) + [Test] + public void Compute_Step5_OvershootWithoutEnoughSnapshots() + { + // add two old enough snapshots + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); + SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + // -> interpolation time is already at '1' at the end. + // -> compute will add 0.5 deltaTime + // -> so we should NOT overshoot aka extrapolate beyond second snap. + double localTime = 3; + double deltaTime = 0.5; + double interpolationTime = 1; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started at the end = 1 + // and deltaTime is 0.5, so it's at 1.5 internally. + // + // BUT there's NO reason to overshoot interpolationTime if there's + // no other snapshots to move to. + // interpolationTime overshoot is only for smooth transitions WHILE + // moving. + // for example, if we keep overshooting to 100, then we would + // instantly skip the next 20 snapshots. + // => so it should be capped at second.remoteTime + Assert.That(interpolationTime, Is.EqualTo(1)); + // buffer should be untouched, we are still interpolating between the two + Assert.That(buffer.Count, Is.EqualTo(2)); + // computed snapshot should NOT extrapolate beyond second snap. + Assert.That(computed.value, Is.EqualTo(2).Within(Mathf.Epsilon)); + } + + // fifth step: interpolation time overshoots the end while having more + // snapshots available. + // BUT: the next snapshot isn't old enough yet. + // we shouldn't move there until old enough. + // for the same reason we don't move to first, second + // until they are old enough. + // => always need to be 'bufferTime' old. + [Test] + public void Compute_Step5_OvershootWithEnoughSnapshots_NextIsntOldEnough() + { + // add two old enough snapshots + // (localTime - bufferTime) + // + // IMPORTANT: second.time needs to be != second.time-first.time + // to guarantee that we cap interpolationTime (which is + // RELATIVE from 0..delta) at delta, not at second.time. + // this was a bug before. + SimpleSnapshot first = new SimpleSnapshot(1, 1, 1); + SimpleSnapshot second = new SimpleSnapshot(2, 2, 2); + // IMPORTANT: third snapshot needs to be: + // - a different time delta + // to test if overflow is correct if deltas are different. + // it's not obvious if we ever use t ratio between [0,1] where an + // overflow of 0.1 between A,B could speed up B,C interpolation if + // that's not the same delta, since t is a ratio. + // - a different value delta to check if it really _interpolates_, + // not just extrapolates further after A,B + SimpleSnapshot third = new SimpleSnapshot(4, 4, 4); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + buffer.Add(third.remoteTimestamp, third); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + // -> interpolation time is already at '1' at the end. + // -> compute will add 0.5 deltaTime + // -> so we overshoot beyond the second one and move to the next + // + // localTime is at 4 + // third snapshot localTime is at 4. + // bufferTime is 2, so it is NOT old enough and we should wait! + double localTime = 4; + double deltaTime = 0.5; + double interpolationTime = 1; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should still spit out a result between first & second. + Assert.That(result, Is.True); + // interpolation started at the end = 1 + // and deltaTime is 0.5, so we were at 1.5 internally. + // + // BUT there's NO reason to overshoot interpolationTime while we + // wait for the next snapshot which isn't old enough. + // we stopped movement anyway. + // interpolationTime overshoot is only for smooth transitions WHILE + // moving. + // for example, if we overshoot to 100 while waiting, then we would + // instantly skip the next 20 snapshots. + // => so it should be capped at max + // => which is always 0..delta, NOT first.time .. second.time!! + Assert.That(interpolationTime, Is.EqualTo(1)); + // buffer should be untouched. shouldn't have moved to third yet. + Assert.That(buffer.Count, Is.EqualTo(3)); + // computed snapshot should be all the way at second snapshot. + Assert.That(computed.value, Is.EqualTo(2).Within(Mathf.Epsilon)); + } + + // fifth step: interpolation time overshoots the end while having more + // snapshots available. + [Test] + public void Compute_Step5_OvershootWithEnoughSnapshots_MovesToNextSnapshotIfOldEnough() + { + // add two old enough snapshots + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); + SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); + // IMPORTANT: third snapshot needs to be: + // - a different time delta + // to test if overflow is correct if deltas are different. + // it's not obvious if we ever use t ratio between [0,1] where an + // overflow of 0.1 between A,B could speed up B,C interpolation if + // that's not the same delta, since t is a ratio. + // - a different value delta to check if it really _interpolates_, + // not just extrapolates further after A,B + SimpleSnapshot third = new SimpleSnapshot(3, 3, 4); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + buffer.Add(third.remoteTimestamp, third); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + // -> interpolation time is already at '1' at the end. + // -> compute will add 0.5 deltaTime + // -> so we overshoot beyond the second one and move to the next + // + // localTime is 5. third snapshot localTime is at 3. + // bufferTime is 2. + // so third is exactly old enough and we should move there. + double localTime = 5; + double deltaTime = 0.5; + double interpolationTime = 1; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started at the end = 1 + // and deltaTime is 0.5, so we were at 1.5 internally. + // we have more snapshots, so we jump to the next and subtract '1' + // 1 + 0.5 = 1.5 => -1 => 0.5 + Assert.That(interpolationTime, Is.EqualTo(0.5)); + // buffer's first entry should have been removed + Assert.That(buffer.Count, Is.EqualTo(2)); + // computed snapshot should be 1/4 way between second and third + // because delta is 2 and interpolationTime is at 0.5 which is 1/4 + Assert.That(computed.value, Is.EqualTo(2.5).Within(Mathf.Epsilon)); + } + + // fifth step: interpolation time overshoots 2x the end while having + // >= 2 more snapshots available. it should correctly jump + // ahead the first pending one to the second one. + [Test] + public void Compute_Step5_OvershootWithEnoughSnapshots_2x_MovesToSecondNextSnapshot() + { + // add two old enough snapshots + // (localTime - bufferTime) + SimpleSnapshot first = new SimpleSnapshot(0, 0, 1); + SimpleSnapshot second = new SimpleSnapshot(1, 1, 2); + // IMPORTANT: third snapshot needs to be: + // - a different time delta + // to test if overflow is correct if deltas are different. + // it's not obvious if we ever use t ratio between [0,1] where an + // overflow of 0.1 between A,B could speed up B,C interpolation if + // that's not the same delta, since t is a ratio. + // - a different value delta to check if it really _interpolates_, + // not just extrapolates further after A,B + SimpleSnapshot third = new SimpleSnapshot(3, 3, 4); + SimpleSnapshot fourth = new SimpleSnapshot(5, 5, 6); + buffer.Add(first.remoteTimestamp, first); + buffer.Add(second.remoteTimestamp, second); + buffer.Add(third.remoteTimestamp, third); + buffer.Add(fourth.remoteTimestamp, fourth); + + // compute with initialized remoteTime and buffer time of 2 seconds + // and a delta time to be sure that we move along it no matter what. + // -> interpolation time is already at '1' at the end. + // -> compute will add 1.5 deltaTime + // -> so we should overshoot beyond second and third even + // + // localTime is 7. fourth snapshot localTime is at 5. + // bufferTime is 2. + // so fourth is exactly old enough and we should move there. + double localTime = 7; + double deltaTime = 2.5; + double interpolationTime = 1; + float bufferTime = 2; + int catchupThreshold = Int32.MaxValue; + float catchupMultiplier = 0; + bool result = SnapshotInterpolation.Compute(localTime, deltaTime, ref interpolationTime, bufferTime, buffer, catchupThreshold, catchupMultiplier, SimpleSnapshot.Interpolate, out SimpleSnapshot computed); + + // should spit out the interpolated snapshot + Assert.That(result, Is.True); + // interpolation started at the end = 1 + // and deltaTime is 2.5, so we were at 4.5 internally. + // we have more snapshots, so we: + // * jump to third, subtract delta of 1-0 = 1 => 2.5 + // * jump to fourth, subtract delta of 3-1 = 2 => 0.5 + // * end up at 0.5 again, between third and fourth + Assert.That(interpolationTime, Is.EqualTo(0.5)); + // buffer's first entry should have been removed + Assert.That(buffer.Count, Is.EqualTo(2)); + // computed snapshot should be 1/4 way between second and third + // because delta is 2 and interpolationTime is at 0.5 which is 1/4 + Assert.That(computed.value, Is.EqualTo(4.5).Within(Mathf.Epsilon)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs.meta b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs.meta new file mode 100644 index 000000000..31edb4df0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SnapshotInterpolationTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d061a81bee3e4558b19b0d4dbedc8f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/StructMessagesTests.cs b/Assets/Mirror/Tests/Editor/StructMessagesTests.cs new file mode 100644 index 000000000..79f03fca1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/StructMessagesTests.cs @@ -0,0 +1,36 @@ +using NUnit.Framework; + +namespace Mirror.Tests.StructMessages +{ + public struct SomeStructMessage : NetworkMessage + { + public int someValue; + } + + [TestFixture] + public class StructMessagesTests + { + [Test] + public void SerializeAreAddedWhenEmptyInStruct() + { + NetworkWriter writer = new NetworkWriter(); + + const int someValue = 3; + writer.Write(new SomeStructMessage + { + someValue = someValue, + }); + + byte[] arr = writer.ToArray(); + + NetworkReader reader = new NetworkReader(arr); + SomeStructMessage received = reader.Read(); + + Assert.AreEqual(someValue, received.someValue); + + int writeLength = writer.Position; + int readLength = reader.Position; + Assert.That(writeLength == readLength, $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/StructMessagesTests.cs.meta b/Assets/Mirror/Tests/Editor/StructMessagesTests.cs.meta new file mode 100644 index 000000000..f44bd63aa --- /dev/null +++ b/Assets/Mirror/Tests/Editor/StructMessagesTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8268c9b6cd466ae4c806291bdc88c0e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs b/Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs new file mode 100644 index 000000000..e44927c4d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs @@ -0,0 +1,353 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Mirror.Tests +{ + [TestFixture] + public class SyncDictionaryTest + { + SyncDictionary serverSyncDictionary; + SyncDictionary clientSyncDictionary; + int serverSyncDictionaryDirtyCalled; + int clientSyncDictionaryDirtyCalled; + + void SerializeAllTo(T fromList, T toList) where T : SyncObject + { + NetworkWriter writer = new NetworkWriter(); + fromList.OnSerializeAll(writer); + NetworkReader reader = new NetworkReader(writer.ToArray()); + toList.OnDeserializeAll(reader); + } + + void SerializeDeltaTo(T fromList, T toList) where T : SyncObject + { + NetworkWriter writer = new NetworkWriter(); + fromList.OnSerializeDelta(writer); + NetworkReader reader = new NetworkReader(writer.ToArray()); + toList.OnDeserializeDelta(reader); + fromList.ClearChanges(); + } + + [SetUp] + public void SetUp() + { + serverSyncDictionary = new SyncDictionary(); + clientSyncDictionary = new SyncDictionary(); + + // add some data to the list + serverSyncDictionary.Add(0, "Hello"); + serverSyncDictionary.Add(1, "World"); + serverSyncDictionary.Add(2, "!"); + SerializeAllTo(serverSyncDictionary, clientSyncDictionary); + + // set up dirty callbacks for testing. + // AFTER adding the example data. we already know we added that data. + serverSyncDictionaryDirtyCalled = 0; + clientSyncDictionaryDirtyCalled = 0; + serverSyncDictionary.OnDirty = () => ++serverSyncDictionaryDirtyCalled; + clientSyncDictionary.OnDirty = () => ++clientSyncDictionaryDirtyCalled; + } + + [Test] + public void TestInit() + { + Dictionary comparer = new Dictionary + { + [0] = "Hello", + [1] = "World", + [2] = "!" + }; + Assert.That(clientSyncDictionary[0], Is.EqualTo("Hello")); + Assert.That(clientSyncDictionary, Is.EquivalentTo(comparer)); + } + + // test the '= List{1,2,3}' constructor. + // it calls .Add(1); .Add(2); .Add(3) in the constructor. + // (the OnDirty change broke this and we didn't have a test before) + [Test] + public void CurlyBracesConstructor() + { + SyncDictionary dict = new SyncDictionary{{1,"1"}, {2,"2"}, {3,"3"}}; + Assert.That(dict.Count, Is.EqualTo(3)); + } + + [Test] + public void TestAdd() + { + serverSyncDictionary.Add(4, "yay"); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary.ContainsKey(4)); + Assert.That(clientSyncDictionary[4], Is.EqualTo("yay")); + } + + [Test] + public void TestClear() + { + serverSyncDictionary.Clear(); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(serverSyncDictionary, Is.EquivalentTo(new SyncDictionary())); + } + + [Test] + public void TestSet() + { + serverSyncDictionary[1] = "yay"; + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary.ContainsKey(1)); + Assert.That(clientSyncDictionary[1], Is.EqualTo("yay")); + } + + [Test] + public void TestBareSet() + { + serverSyncDictionary[4] = "yay"; + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary.ContainsKey(4)); + Assert.That(clientSyncDictionary[4], Is.EqualTo("yay")); + } + + [Test] + public void TestBareSetNull() + { + serverSyncDictionary[4] = null; + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary[4], Is.Null); + Assert.That(clientSyncDictionary.ContainsKey(4)); + } + + [Test] + public void TestConsecutiveSet() + { + serverSyncDictionary[1] = "yay"; + serverSyncDictionary[1] = "world"; + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary[1], Is.EqualTo("world")); + } + + [Test] + public void TestNullSet() + { + serverSyncDictionary[1] = null; + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary.ContainsKey(1)); + Assert.That(clientSyncDictionary[1], Is.Null); + } + + [Test] + public void TestRemove() + { + serverSyncDictionary.Remove(1); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(!clientSyncDictionary.ContainsKey(1)); + } + + [Test] + public void TestMultSync() + { + serverSyncDictionary.Add(10, "1"); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + // add some delta and see if it applies + serverSyncDictionary.Add(11, "2"); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary.ContainsKey(10)); + Assert.That(clientSyncDictionary[10], Is.EqualTo("1")); + Assert.That(clientSyncDictionary.ContainsKey(11)); + Assert.That(clientSyncDictionary[11], Is.EqualTo("2")); + } + + [Test] + public void TestContains() + { + Assert.That(!clientSyncDictionary.Contains(new KeyValuePair(2, "Hello"))); + serverSyncDictionary[2] = "Hello"; + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(clientSyncDictionary.Contains(new KeyValuePair(2, "Hello"))); + } + + [Test] + public void CallbackTest() + { + bool called = false; + clientSyncDictionary.Callback += (op, index, item) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncDictionary.Operation.OP_ADD)); + Assert.That(index, Is.EqualTo(3)); + Assert.That(item, Is.EqualTo("yay")); + Assert.That(clientSyncDictionary[index], Is.EqualTo("yay")); + + }; + serverSyncDictionary.Add(3, "yay"); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(called, Is.True); + } + + [Test] + public void ServerCallbackTest() + { + bool called = false; + serverSyncDictionary.Callback += (op, index, item) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncDictionary.Operation.OP_ADD)); + Assert.That(index, Is.EqualTo(3)); + Assert.That(item, Is.EqualTo("yay")); + Assert.That(serverSyncDictionary[index], Is.EqualTo("yay")); + }; + serverSyncDictionary[3] = "yay"; + Assert.That(called, Is.True); + } + + [Test] + public void CallbackRemoveTest() + { + bool called = false; + clientSyncDictionary.Callback += (op, key, item) => + { + called = true; + Assert.That(op, Is.EqualTo(SyncDictionary.Operation.OP_REMOVE)); + Assert.That(item, Is.EqualTo("World")); + }; + serverSyncDictionary.Remove(1); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + Assert.That(called, Is.True); + } + + [Test] + public void CountTest() + { + Assert.That(serverSyncDictionary.Count, Is.EqualTo(3)); + } + + [Test] + public void CopyToTest() + { + KeyValuePair[] data = new KeyValuePair[3]; + + clientSyncDictionary.CopyTo(data, 0); + + Assert.That(data, Is.EquivalentTo(new KeyValuePair[] + { + new KeyValuePair(0, "Hello"), + new KeyValuePair(1, "World"), + new KeyValuePair(2, "!"), + + })); + } + + [Test] + public void CopyToOutOfRangeTest() + { + KeyValuePair[] data = new KeyValuePair[3]; + + Assert.Throws(typeof(ArgumentOutOfRangeException), delegate + { + clientSyncDictionary.CopyTo(data, -1); + }); + } + + [Test] + public void CopyToOutOfBoundsTest() + { + KeyValuePair[] data = new KeyValuePair[3]; + + Assert.Throws(typeof(ArgumentException), delegate + { + clientSyncDictionary.CopyTo(data, 2); + }); + } + + [Test] + public void TestRemovePair() + { + KeyValuePair data = new KeyValuePair(0, "Hello"); + + serverSyncDictionary.Remove(data); + + Assert.That(serverSyncDictionary, Is.EquivalentTo(new KeyValuePair[] + { + new KeyValuePair(1, "World"), + new KeyValuePair(2, "!"), + })); + } + + [Test] + public void ReadOnlyTest() + { + Assert.That(serverSyncDictionary.IsReadOnly, Is.False); + Assert.That(clientSyncDictionary.IsReadOnly, Is.True); + } + + [Test] + public void WritingToReadOnlyThrows() + { + Assert.Throws(() => clientSyncDictionary.Add(50, "fail")); + } + + [Test] + public void DirtyTest() + { + // Sync Delta to clear dirty + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + + // nothing to send + Assert.That(serverSyncDictionaryDirtyCalled, Is.EqualTo(0)); + + // something has changed + serverSyncDictionary.Add(15, "yay"); + Assert.That(serverSyncDictionaryDirtyCalled, Is.EqualTo(1)); + SerializeDeltaTo(serverSyncDictionary, clientSyncDictionary); + } + + [Test] + public void ObjectCanBeReusedAfterReset() + { + clientSyncDictionary.Reset(); + + // make old client the host + SyncDictionary hostList = clientSyncDictionary; + SyncDictionary clientList2 = new SyncDictionary(); + + Assert.That(hostList.IsReadOnly, Is.False); + + // Check Add and Sync without errors + hostList.Add(30, "hello"); + hostList.Add(35, "world"); + SerializeDeltaTo(hostList, clientList2); + } + + [Test] + public void ResetShouldSetReadOnlyToFalse() + { + clientSyncDictionary.Reset(); + Assert.That(clientSyncDictionary.IsReadOnly, Is.False); + } + + [Test] + public void ResetShouldClearChanges() + { + serverSyncDictionary.Reset(); + Assert.That(serverSyncDictionary.GetChangeCount(), Is.Zero); + } + + [Test] + public void ResetShouldClearItems() + { + serverSyncDictionary.Reset(); + Assert.That(serverSyncDictionary, Is.Empty); + } + + [Test] + public void IsRecording() + { + // shouldn't record changes if IsRecording() returns false + serverSyncDictionary.ClearChanges(); + serverSyncDictionary.IsRecording = () => false; + serverSyncDictionary[42] = null; + Assert.That(serverSyncDictionary.GetChangeCount(), Is.EqualTo(0)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs.meta new file mode 100644 index 000000000..552e96ded --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncDictionaryTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cadf48c3662efac4181b91f5c9c88774 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncListClassTest.cs b/Assets/Mirror/Tests/Editor/SyncListClassTest.cs new file mode 100644 index 000000000..f6ed2b402 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncListClassTest.cs @@ -0,0 +1,78 @@ +using System.Linq; +using NUnit.Framework; + +namespace Mirror.Tests +{ + class TestObjectBehaviour : NetworkBehaviour + { + // note synclists must be a property of a NetworkBehavior so that + // the weaver generates the reader and writer for the object + public readonly SyncList myList = new SyncList(); + } + + public class SyncListClassTest + { + [Test] + public void RemoveShouldRemoveItem() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + SyncListTest.SerializeAllTo(serverList, clientList); + + // add some items + TestObject item1 = new TestObject { id = 1, text = "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nostrum ullam aliquid perferendis, aut nihil sunt quod ipsum corporis a. Cupiditate, alias. Commodi, molestiae distinctio repellendus dolor similique delectus inventore eum." }; + serverList.Add(item1); + TestObject item2 = new TestObject { id = 2, text = "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nostrum ullam aliquid perferendis, aut nihil sunt quod ipsum corporis a. Cupiditate, alias. Commodi, molestiae distinctio repellendus dolor similique delectus inventore eum." }; + serverList.Add(item2); + + // sync + SyncListTest.SerializeDeltaTo(serverList, clientList); + + // clear all items + serverList.Remove(item1); + + // sync + SyncListTest.SerializeDeltaTo(serverList, clientList); + + Assert.IsFalse(clientList.Any(x => x.id == item1.id)); + Assert.IsTrue(clientList.Any(x => x.id == item2.id)); + } + + [Test] + public void ClearShouldClearAll() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + SyncListTest.SerializeAllTo(serverList, clientList); + + // add some items + TestObject item1 = new TestObject { id = 1, text = "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nostrum ullam aliquid perferendis, aut nihil sunt quod ipsum corporis a. Cupiditate, alias. Commodi, molestiae distinctio repellendus dolor similique delectus inventore eum." }; + serverList.Add(item1); + TestObject item2 = new TestObject { id = 2, text = "Lorem ipsum dolor sit, amet consectetur adipisicing elit. Nostrum ullam aliquid perferendis, aut nihil sunt quod ipsum corporis a. Cupiditate, alias. Commodi, molestiae distinctio repellendus dolor similique delectus inventore eum." }; + serverList.Add(item2); + + // sync + SyncListTest.SerializeDeltaTo(serverList, clientList); + + // clear all items + serverList.Clear(); + + // sync + SyncListTest.SerializeDeltaTo(serverList, clientList); + + Assert.That(clientList.Count, Is.Zero); + + Assert.IsFalse(clientList.Any(x => x.id == item1.id)); + Assert.IsFalse(clientList.Any(x => x.id == item2.id)); + } + } + + [System.Serializable] + public class TestObject + { + public int id; + public string text; + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncListClassTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncListClassTest.cs.meta new file mode 100644 index 000000000..d26a39d04 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncListClassTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3c4bb793f509fcb47bdf830656826f2b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncListStructTest.cs b/Assets/Mirror/Tests/Editor/SyncListStructTest.cs new file mode 100644 index 000000000..6c3a6e565 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncListStructTest.cs @@ -0,0 +1,73 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + class TestPlayerBehaviour : NetworkBehaviour + { + // note synclists must be a property of a NetworkBehavior so that + // the weaver generates the reader and writer for the object + public readonly SyncList myList = new SyncList(); + } + + public class SyncListStructTest + { + [Test] + public void ListIsDirtyWhenModifingAndSettingStruct() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + // set up dirty callback + int serverListDirtyCalled = 0; + serverList.OnDirty = () => ++serverListDirtyCalled; + + SyncListTest.SerializeAllTo(serverList, clientList); + serverList.Add(new TestPlayer { item = new TestItem { price = 10 } }); + Assert.That(serverListDirtyCalled, Is.EqualTo(1)); + SyncListTest.SerializeDeltaTo(serverList, clientList); + serverListDirtyCalled = 0; + + TestPlayer player = serverList[0]; + player.item.price = 15; + serverList[0] = player; + + Assert.That(serverListDirtyCalled, Is.EqualTo(1)); + } + + [Test] + public void OldValueShouldNotBeNewValue() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + SyncListTest.SerializeAllTo(serverList, clientList); + serverList.Add(new TestPlayer { item = new TestItem { price = 10 } }); + SyncListTest.SerializeDeltaTo(serverList, clientList); + + TestPlayer player = serverList[0]; + player.item.price = 15; + serverList[0] = player; + + bool callbackCalled = false; + clientList.Callback += (SyncList.Operation op, int itemIndex, TestPlayer oldItem, TestPlayer newItem) => + { + Assert.That(op == SyncList.Operation.OP_SET, Is.True); + Assert.That(itemIndex, Is.EqualTo(0)); + Assert.That(oldItem.item.price, Is.EqualTo(10)); + Assert.That(newItem.item.price, Is.EqualTo(15)); + callbackCalled = true; + }; + + SyncListTest.SerializeDeltaTo(serverList, clientList); + Assert.IsTrue(callbackCalled); + } + } + + public struct TestPlayer + { + public TestItem item; + } + public struct TestItem + { + public float price; + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncListStructTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncListStructTest.cs.meta new file mode 100644 index 000000000..b3a20facc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncListStructTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cad81f2aee838a4ababa9c8ee23a700 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncListTest.cs b/Assets/Mirror/Tests/Editor/SyncListTest.cs new file mode 100644 index 000000000..9e33ccec7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncListTest.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Mirror.Tests +{ + [TestFixture] + public class SyncListTest + { + SyncList serverSyncList; + SyncList clientSyncList; + int serverSyncListDirtyCalled; + int clientSyncListDirtyCalled; + + public static void SerializeAllTo(T fromList, T toList) where T : SyncObject + { + NetworkWriter writer = new NetworkWriter(); + fromList.OnSerializeAll(writer); + NetworkReader reader = new NetworkReader(writer.ToArray()); + toList.OnDeserializeAll(reader); + + int writeLength = writer.Position; + int readLength = reader.Position; + Assert.That(writeLength == readLength, $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}"); + + } + + public static void SerializeDeltaTo(T fromList, T toList) where T : SyncObject + { + NetworkWriter writer = new NetworkWriter(); + fromList.OnSerializeDelta(writer); + NetworkReader reader = new NetworkReader(writer.ToArray()); + toList.OnDeserializeDelta(reader); + fromList.ClearChanges(); + + int writeLength = writer.Position; + int readLength = reader.Position; + Assert.That(writeLength == readLength, $"OnSerializeDelta and OnDeserializeDelta calls write the same amount of data\n writeLength={writeLength}\n readLength={readLength}"); + } + + [SetUp] + public void SetUp() + { + serverSyncList = new SyncList(); + clientSyncList = new SyncList(); + + // add some data to the list + serverSyncList.Add("Hello"); + serverSyncList.Add("World"); + serverSyncList.Add("!"); + SerializeAllTo(serverSyncList, clientSyncList); + + // set up dirty callbacks for testing + // AFTER adding the example data. we already know we added that data. + serverSyncList.OnDirty = () => ++serverSyncListDirtyCalled; + clientSyncList.OnDirty = () => ++clientSyncListDirtyCalled; + serverSyncListDirtyCalled = 0; + clientSyncListDirtyCalled = 0; + } + + [Test] + public void TestInit() + { + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "World", "!" })); + } + + // test the '= List{1,2,3}' constructor. + // it calls .Add(1); .Add(2); .Add(3) in the constructor. + // (the OnDirty change broke this and we didn't have a test before) + [Test] + public void CurlyBracesConstructor() + { + SyncList list = new SyncList{1,2,3}; + Assert.That(list.Count, Is.EqualTo(3)); + } + + [Test] + public void TestAdd() + { + serverSyncList.Add("yay"); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "World", "!", "yay" })); + } + + [Test] + public void TestAddRange() + { + serverSyncList.AddRange(new[] { "One", "Two", "Three" }); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EqualTo(new[] { "Hello", "World", "!", "One", "Two", "Three" })); + } + + [Test] + public void TestClear() + { + serverSyncList.Clear(); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new string[] {})); + } + + [Test] + public void TestInsert() + { + serverSyncList.Insert(0, "yay"); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "yay", "Hello", "World", "!" })); + } + + [Test] + public void TestInsertRange() + { + serverSyncList.InsertRange(1, new[] { "One", "Two", "Three" }); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EqualTo(new[] { "Hello", "One", "Two", "Three", "World", "!" })); + } + + [Test] + public void TestSet() + { + serverSyncList[1] = "yay"; + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList[1], Is.EqualTo("yay")); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "yay", "!" })); + } + + [Test] + public void TestSetNull() + { + serverSyncList[1] = null; + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList[1], Is.EqualTo(null)); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", null, "!" })); + serverSyncList[1] = "yay"; + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "yay", "!" })); + } + + [Test] + public void TestRemoveAll() + { + serverSyncList.RemoveAll(entry => entry.Contains("l")); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "!" })); + } + + [Test] + public void TestRemoveAllNone() + { + serverSyncList.RemoveAll(entry => entry == "yay"); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "World", "!" })); + } + + [Test] + public void TestRemoveAt() + { + serverSyncList.RemoveAt(1); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "!" })); + } + + [Test] + public void TestRemove() + { + serverSyncList.Remove("World"); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "!" })); + } + + [Test] + public void TestFindIndex() + { + int index = serverSyncList.FindIndex(entry => entry == "World"); + Assert.That(index, Is.EqualTo(1)); + } + + [Test] + public void TestFind() + { + string element = serverSyncList.Find(entry => entry == "World"); + Assert.That(element, Is.EqualTo("World")); + } + + [Test] + public void TestNoFind() + { + string nonexistent = serverSyncList.Find(entry => entry == "yay"); + Assert.That(nonexistent, Is.Null); + } + + [Test] + public void TestFindAll() + { + List results = serverSyncList.FindAll(entry => entry.Contains("l")); + Assert.That(results, Is.EquivalentTo(new[] { "Hello", "World" })); + } + + [Test] + public void TestFindAllNonExistent() + { + List nonexistent = serverSyncList.FindAll(entry => entry == "yay"); + Assert.That(nonexistent, Is.Empty); + } + + [Test] + public void TestMultSync() + { + serverSyncList.Add("1"); + SerializeDeltaTo(serverSyncList, clientSyncList); + // add some delta and see if it applies + serverSyncList.Add("2"); + SerializeDeltaTo(serverSyncList, clientSyncList); + Assert.That(clientSyncList, Is.EquivalentTo(new[] { "Hello", "World", "!", "1", "2" })); + } + + [Test] + public void SyncListIntTest() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + serverList.Add(1); + serverList.Add(2); + serverList.Add(3); + SerializeDeltaTo(serverList, clientList); + + Assert.That(clientList, Is.EquivalentTo(new[] { 1, 2, 3 })); + } + + [Test] + public void SyncListBoolTest() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + serverList.Add(true); + serverList.Add(false); + serverList.Add(true); + SerializeDeltaTo(serverList, clientList); + + Assert.That(clientList, Is.EquivalentTo(new[] { true, false, true })); + } + + [Test] + public void SyncListUIntTest() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + serverList.Add(1U); + serverList.Add(2U); + serverList.Add(3U); + SerializeDeltaTo(serverList, clientList); + + Assert.That(clientList, Is.EquivalentTo(new[] { 1U, 2U, 3U })); + } + + [Test] + public void SyncListFloatTest() + { + SyncList serverList = new SyncList(); + SyncList clientList = new SyncList(); + + serverList.Add(1.0F); + serverList.Add(2.0F); + serverList.Add(3.0F); + SerializeDeltaTo(serverList, clientList); + + Assert.That(clientList, Is.EquivalentTo(new[] { 1.0F, 2.0F, 3.0F })); + } + + [Test] + public void CallbackTest() + { + bool called = false; + + clientSyncList.Callback += (op, index, oldItem, newItem) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncList.Operation.OP_ADD)); + Assert.That(index, Is.EqualTo(3)); + Assert.That(oldItem, Is.EqualTo(default(string))); + Assert.That(newItem, Is.EqualTo("yay")); + }; + + serverSyncList.Add("yay"); + SerializeDeltaTo(serverSyncList, clientSyncList); + + Assert.That(called, Is.True); + } + + [Test] + public void CallbackRemoveTest() + { + bool called = false; + + clientSyncList.Callback += (op, index, oldItem, newItem) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncList.Operation.OP_REMOVEAT)); + Assert.That(oldItem, Is.EqualTo("World")); + Assert.That(newItem, Is.EqualTo(default(string))); + }; + serverSyncList.Remove("World"); + SerializeDeltaTo(serverSyncList, clientSyncList); + + Assert.That(called, Is.True); + } + + [Test] + public void CallbackRemoveAtTest() + { + bool called = false; + + clientSyncList.Callback += (op, index, oldItem, newItem) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncList.Operation.OP_REMOVEAT)); + Assert.That(index, Is.EqualTo(1)); + Assert.That(oldItem, Is.EqualTo("World")); + Assert.That(newItem, Is.EqualTo(default(string))); + }; + + serverSyncList.RemoveAt(1); + SerializeDeltaTo(serverSyncList, clientSyncList); + + Assert.That(called, Is.True); + } + + [Test] + public void CountTest() + { + Assert.That(serverSyncList.Count, Is.EqualTo(3)); + } + + [Test] + public void ReadOnlyTest() + { + Assert.That(serverSyncList.IsReadOnly, Is.False); + Assert.That(clientSyncList.IsReadOnly, Is.True); + } + [Test] + public void WritingToReadOnlyThrows() + { + Assert.Throws(() => { clientSyncList.Add("fail"); }); + } + + [Test] + public void DirtyTest() + { + // Sync Delta to clear dirty + Assert.That(serverSyncListDirtyCalled, Is.EqualTo(0)); + SerializeDeltaTo(serverSyncList, clientSyncList); + + // nothing to send + Assert.That(serverSyncListDirtyCalled, Is.EqualTo(0)); + + // something has changed + serverSyncList.Add("1"); + Assert.That(serverSyncListDirtyCalled, Is.EqualTo(1)); + SerializeDeltaTo(serverSyncList, clientSyncList); + } + + [Test] + public void ObjectCanBeReusedAfterReset() + { + clientSyncList.Reset(); + + // make old client the host + SyncList hostList = clientSyncList; + SyncList clientList2 = new SyncList(); + + Assert.That(hostList.IsReadOnly, Is.False); + + // Check Add and Sync without errors + hostList.Add("hello"); + hostList.Add("world"); + SerializeDeltaTo(hostList, clientList2); + } + + [Test] + public void ResetShouldSetReadOnlyToFalse() + { + clientSyncList.Reset(); + + Assert.That(clientSyncList.IsReadOnly, Is.False); + } + + [Test] + public void ResetShouldClearChanges() + { + serverSyncList.Reset(); + + Assert.That(serverSyncList.GetChangeCount(), Is.Zero); + } + + [Test] + public void ResetShouldClearItems() + { + serverSyncList.Reset(); + + Assert.That(serverSyncList, Is.Empty); + } + + [Test] + public void IsRecording() + { + // shouldn't record changes if IsRecording() returns false + serverSyncList.ClearChanges(); + serverSyncList.IsRecording = () => false; + serverSyncList.Add("42"); + Assert.That(serverSyncList.GetChangeCount(), Is.EqualTo(0)); + } + } + + public static class SyncObjectTestMethods + { + public static uint GetChangeCount(this SyncObject syncObject) + { + using (PooledNetworkWriter writer = NetworkWriterPool.GetWriter()) + { + syncObject.OnSerializeDelta(writer); + + using (PooledNetworkReader reader = NetworkReaderPool.GetReader(writer.ToArraySegment())) + { + return reader.ReadUInt(); + } + } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncListTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncListTest.cs.meta new file mode 100644 index 000000000..9b55701a4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncListTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a937d4274ff484d769209f2e0b0c1d8a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncSetTest.cs b/Assets/Mirror/Tests/Editor/SyncSetTest.cs new file mode 100644 index 000000000..bb106c0ce --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncSetTest.cs @@ -0,0 +1,322 @@ +using System; +using System.Collections.Generic; +using NUnit.Framework; + +namespace Mirror.Tests +{ + [TestFixture] + public class SyncSetTest + { + SyncHashSet serverSyncSet; + SyncHashSet clientSyncSet; + int serverSyncSetDirtyCalled; + int clientSyncSetDirtyCalled; + + void SerializeAllTo(T fromList, T toList) where T : SyncObject + { + NetworkWriter writer = new NetworkWriter(); + fromList.OnSerializeAll(writer); + NetworkReader reader = new NetworkReader(writer.ToArray()); + toList.OnDeserializeAll(reader); + } + + void SerializeDeltaTo(T fromList, T toList) where T : SyncObject + { + NetworkWriter writer = new NetworkWriter(); + fromList.OnSerializeDelta(writer); + NetworkReader reader = new NetworkReader(writer.ToArray()); + toList.OnDeserializeDelta(reader); + fromList.ClearChanges(); + } + + [SetUp] + public void SetUp() + { + serverSyncSet = new SyncHashSet(); + clientSyncSet = new SyncHashSet(); + + // add some data to the list + serverSyncSet.Add("Hello"); + serverSyncSet.Add("World"); + serverSyncSet.Add("!"); + SerializeAllTo(serverSyncSet, clientSyncSet); + + // set up dirty callbacks for testing + // AFTER adding the example data. we already know we added that data. + serverSyncSet.OnDirty = () => ++serverSyncSetDirtyCalled; + clientSyncSet.OnDirty = () => ++clientSyncSetDirtyCalled; + serverSyncSetDirtyCalled = 0; + clientSyncSetDirtyCalled = 0; + } + + [Test] + public void TestInit() + { + Assert.That(serverSyncSet, Is.EquivalentTo(new[] { "Hello", "World", "!" })); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "World", "!" })); + } + + // test the '= List{1,2,3}' constructor. + // it calls .Add(1); .Add(2); .Add(3) in the constructor. + // (the OnDirty change broke this and we didn't have a test before) + [Test] + public void CurlyBracesConstructor() + { + SyncHashSet set = new SyncHashSet{1,2,3}; + Assert.That(set.Count, Is.EqualTo(3)); + } + + [Test] + public void TestAdd() + { + serverSyncSet.Add("yay"); + Assert.That(serverSyncSetDirtyCalled, Is.EqualTo(1)); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "World", "!", "yay" })); + } + + [Test] + public void TestClear() + { + serverSyncSet.Clear(); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new string[] {})); + } + + [Test] + public void TestRemove() + { + serverSyncSet.Remove("World"); + Assert.That(serverSyncSetDirtyCalled, Is.EqualTo(1)); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "!" })); + } + + [Test] + public void TestMultSync() + { + serverSyncSet.Add("1"); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + // add some delta and see if it applies + serverSyncSet.Add("2"); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "Hello", "World", "!", "1", "2" })); + } + + [Test] + public void CallbackTest() + { + bool called = false; + + clientSyncSet.Callback += (op, item) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncHashSet.Operation.OP_ADD)); + Assert.That(item, Is.EqualTo("yay")); + }; + + serverSyncSet.Add("yay"); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + + Assert.That(called, Is.True); + } + + [Test] + public void CallbackRemoveTest() + { + bool called = false; + + clientSyncSet.Callback += (op, item) => + { + called = true; + + Assert.That(op, Is.EqualTo(SyncHashSet.Operation.OP_REMOVE)); + Assert.That(item, Is.EqualTo("World")); + }; + serverSyncSet.Remove("World"); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + + Assert.That(called, Is.True); + } + + [Test] + public void CountTest() + { + Assert.That(serverSyncSet.Count, Is.EqualTo(3)); + } + [Test] + public void TestExceptWith() + { + serverSyncSet.ExceptWith(new[] { "World", "Hello" }); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "!" })); + } + + [Test] + public void TestExceptWithSelf() + { + serverSyncSet.ExceptWith(serverSyncSet); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new String[] {})); + } + + [Test] + public void TestIntersectWith() + { + serverSyncSet.IntersectWith(new[] { "World", "Hello" }); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello" })); + } + + [Test] + public void TestIntersectWithSet() + { + serverSyncSet.IntersectWith(new HashSet { "World", "Hello" }); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello" })); + } + + [Test] + public void TestIsProperSubsetOf() + { + Assert.That(clientSyncSet.IsProperSubsetOf(new[] { "World", "Hello", "!", "pepe" })); + } + + [Test] + public void TestIsProperSubsetOfSet() + { + Assert.That(clientSyncSet.IsProperSubsetOf(new HashSet { "World", "Hello", "!", "pepe" })); + } + + [Test] + public void TestIsNotProperSubsetOf() + { + Assert.That(clientSyncSet.IsProperSubsetOf(new[] { "World", "!", "pepe" }), Is.False); + } + + [Test] + public void TestIsProperSuperSetOf() + { + Assert.That(clientSyncSet.IsProperSupersetOf(new[] { "World", "Hello" })); + } + + [Test] + public void TestIsSubsetOf() + { + Assert.That(clientSyncSet.IsSubsetOf(new[] { "World", "Hello", "!" })); + } + + [Test] + public void TestIsSupersetOf() + { + Assert.That(clientSyncSet.IsSupersetOf(new[] { "World", "Hello" })); + } + + [Test] + public void TestOverlaps() + { + Assert.That(clientSyncSet.Overlaps(new[] { "World", "my", "baby" })); + } + + [Test] + public void TestSetEquals() + { + Assert.That(clientSyncSet.SetEquals(new[] { "World", "Hello", "!" })); + } + + [Test] + public void TestSymmetricExceptWith() + { + serverSyncSet.SymmetricExceptWith(new HashSet { "Hello", "is" }); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "is", "!" })); + } + + [Test] + public void TestSymmetricExceptWithSelf() + { + serverSyncSet.SymmetricExceptWith(serverSyncSet); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new String[] {})); + } + + [Test] + public void TestUnionWith() + { + serverSyncSet.UnionWith(new HashSet { "Hello", "is" }); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello", "is", "!" })); + } + + [Test] + public void TestUnionWithSelf() + { + serverSyncSet.UnionWith(serverSyncSet); + SerializeDeltaTo(serverSyncSet, clientSyncSet); + Assert.That(clientSyncSet, Is.EquivalentTo(new[] { "World", "Hello", "!" })); + } + + [Test] + public void ReadOnlyTest() + { + Assert.That(serverSyncSet.IsReadOnly, Is.False); + Assert.That(clientSyncSet.IsReadOnly, Is.True); + } + + [Test] + public void WritingToReadOnlyThrows() + { + Assert.Throws(() => { clientSyncSet.Add("5"); }); + } + + [Test] + public void ObjectCanBeReusedAfterReset() + { + clientSyncSet.Reset(); + + // make old client the host + SyncHashSet hostList = clientSyncSet; + SyncHashSet clientList2 = new SyncHashSet(); + + Assert.That(hostList.IsReadOnly, Is.False); + + // Check Add and Sync without errors + hostList.Add("1"); + hostList.Add("2"); + hostList.Add("3"); + SerializeDeltaTo(hostList, clientList2); + } + + [Test] + public void ResetShouldSetReadOnlyToFalse() + { + clientSyncSet.Reset(); + Assert.That(clientSyncSet.IsReadOnly, Is.False); + } + + [Test] + public void ResetShouldClearChanges() + { + serverSyncSet.Reset(); + Assert.That(serverSyncSet.GetChangeCount(), Is.Zero); + } + + [Test] + public void ResetShouldClearItems() + { + serverSyncSet.Reset(); + Assert.That(serverSyncSet, Is.Empty); + } + + [Test] + public void IsRecording() + { + // shouldn't record changes if IsRecording() returns false + serverSyncSet.ClearChanges(); + serverSyncSet.IsRecording = () => false; + serverSyncSet.Add("42"); + Assert.That(serverSyncSet.GetChangeCount(), Is.EqualTo(0)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncSetTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncSetTest.cs.meta new file mode 100644 index 000000000..7411ffedd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncSetTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 36dbb64593fa546edb477df3d88b6e1a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs b/Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs new file mode 100644 index 000000000..5bf65e0f7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs @@ -0,0 +1,521 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.SyncVarAttributeTests +{ + class HookBehaviour : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public int value = 0; + + public event Action HookCalled; + + void OnValueChanged(int oldValue, int newValue) + { + HookCalled.Invoke(oldValue, newValue); + } + } + + class GameObjectHookBehaviour : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public GameObject value = null; + + public event Action HookCalled; + + void OnValueChanged(GameObject oldValue, GameObject newValue) + { + HookCalled.Invoke(oldValue, newValue); + } + } + + class NetworkIdentityHookBehaviour : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public NetworkIdentity value = null; + + public event Action HookCalled; + + void OnValueChanged(NetworkIdentity oldValue, NetworkIdentity newValue) + { + HookCalled.Invoke(oldValue, newValue); + } + } + + class NetworkBehaviourHookBehaviour : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public NetworkBehaviourHookBehaviour value = null; + + public event Action HookCalled; + + void OnValueChanged(NetworkBehaviourHookBehaviour oldValue, NetworkBehaviourHookBehaviour newValue) + { + HookCalled.Invoke(oldValue, newValue); + } + } + + class StaticHookBehaviour : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public int value = 0; + + public static event Action HookCalled; + + static void OnValueChanged(int oldValue, int newValue) + { + HookCalled.Invoke(oldValue, newValue); + } + } + + class VirtualHookBase : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public int value = 0; + + public event Action BaseHookCalled; + + protected virtual void OnValueChanged(int oldValue, int newValue) + { + BaseHookCalled.Invoke(oldValue, newValue); + } + } + + class VirtualOverrideHook : VirtualHookBase + { + public event Action OverrideHookCalled; + + protected override void OnValueChanged(int oldValue, int newValue) + { + OverrideHookCalled.Invoke(oldValue, newValue); + } + } + + abstract class AbstractHookBase : NetworkBehaviour + { + [SyncVar(hook = nameof(OnValueChanged))] + public int value = 0; + + protected abstract void OnValueChanged(int oldValue, int newValue); + } + + class AbstractHook : AbstractHookBase + { + public event Action HookCalled; + + protected override void OnValueChanged(int oldValue, int newValue) + { + HookCalled.Invoke(oldValue, newValue); + } + } + + public struct Proportions + { + public byte[] Array; + } + class ImerHook_Ldflda : NetworkBehaviour + { + // to check + public byte[] ldflda_Array; + + [SyncVar(hook = nameof(OnUpdateProportions))] + public Proportions _syncProportions; + + protected void OnUpdateProportions(Proportions old, Proportions new_) + { + // _new is fine with the new values. + // assigning to _syncProportions is fine too. + _syncProportions = new_; + + // loading _syncProportions.Array would still load the original SyncVar, + // not the replacement. so .Array was still null. + // we needed to replace ldflda here. + // + // this throws if it still loads the old _syncProportions after weaving + // because the .Array was still null there. + ldflda_Array = _syncProportions.Array; + Debug.Log("Array= " + ldflda_Array); + } + } + + + // repro for the bug found by David_548219 in discord where setting + // MyStruct.value would throw invalid IL + public struct DavidStruct + { + public int Value; + } + class DavidHookComponent : NetworkBehaviour + { + [SyncVar] public DavidStruct syncvar; + + public override void OnStartServer() + { + syncvar.Value = 42; + } + } + + public class SyncVarAttributeHookTest : SyncVarAttributeTestBase + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // start server & connect client because we need spawn functions + NetworkServer.Listen(1); + ConnectClientBlockingAuthenticatedAndReady(out _); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Hook_CalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out HookBehaviour serverObject, + out _, out _, out HookBehaviour clientObject); + + const int serverValue = 24; + + // change it on server + serverObject.value = serverValue; + + // hook should change it on client + int callCount = 0; + clientObject.HookCalled += (oldValue, newValue) => + { + callCount++; + Assert.That(oldValue, Is.EqualTo(0)); + Assert.That(newValue, Is.EqualTo(serverValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Hook_NotCalledWhenSyncingSameValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out HookBehaviour serverObject, + out _, out _, out HookBehaviour clientObject); + + const int clientValue = 16; + const int serverValue = 16; + + // set both to same values once + serverObject.value = serverValue; + clientObject.value = clientValue; + + // client hook + int callCount = 0; + clientObject.HookCalled += (oldValue, newValue) => + { + callCount++; + }; + + // hook shouldn't be called because both already have same value + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(callCount, Is.EqualTo(0)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void Hook_OnlyCalledOnClient(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out HookBehaviour serverObject, + out _, out _, out HookBehaviour clientObject); + + // set up hooks on server and client + int clientCalled = 0; + int serverCalled = 0; + clientObject.HookCalled += (oldValue, newValue) => ++clientCalled; + serverObject.HookCalled += (oldValue, newValue) => ++serverCalled; + + // change on server + ++serverObject.value; + //++clientObject.value; + + // sync. hook should've only been called on client. + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(clientCalled, Is.EqualTo(1)); + Assert.That(serverCalled, Is.EqualTo(0)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void StaticMethod_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out StaticHookBehaviour serverObject, + out _, out _, out StaticHookBehaviour clientObject); + + const int serverValue = 24; + + // change it on server + serverObject.value = serverValue; + + // hook should change it on client + int hookcallCount = 0; + StaticHookBehaviour.HookCalled += (oldValue, newValue) => + { + hookcallCount++; + Assert.That(oldValue, Is.EqualTo(0)); + Assert.That(newValue, Is.EqualTo(serverValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(hookcallCount, Is.EqualTo(1)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void GameObjectHook_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out GameObjectHookBehaviour serverObject, + out _, out _, out GameObjectHookBehaviour clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out GameObject serverValue, out _, + out GameObject clientValue, out _); + + // change it on server + clientObject.value = null; + serverObject.value = serverValue; + + // hook should change it on client + int callCount = 0; + clientObject.HookCalled += (oldValue, newValue) => + { + callCount++; + Assert.That(oldValue, Is.EqualTo(null)); + Assert.That(newValue, Is.EqualTo(clientValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NetworkIdentityHook_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out NetworkIdentityHookBehaviour serverObject, + out _, out _, out NetworkIdentityHookBehaviour clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverValue, + out _, out NetworkIdentity clientValue); + + // change it on server + serverObject.value = serverValue; + clientObject.value = null; + + // hook should change it on client + int callCount = 0; + clientObject.HookCalled += (oldValue, newValue) => + { + callCount++; + Assert.That(oldValue, Is.EqualTo(null)); + Assert.That(newValue, Is.EqualTo(clientValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void NetworkBehaviourHook_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out NetworkBehaviourHookBehaviour serverObject, + out _, out _, out NetworkBehaviourHookBehaviour clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out _, out NetworkBehaviourHookBehaviour serverValue, + out _, out _, out NetworkBehaviourHookBehaviour clientValue); + + // change it on server + serverObject.value = serverValue; + clientObject.value = null; + + // hook should change it on client + int callCount = 0; + clientObject.HookCalled += (oldValue, newValue) => + { + callCount++; + Assert.That(oldValue, Is.EqualTo(null)); + Assert.That(newValue, Is.EqualTo(clientValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VirtualHook_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out VirtualHookBase serverObject, + out _, out _, out VirtualHookBase clientObject); + + const int clientValue = 10; + const int serverValue = 24; + + // change it on server + serverObject.value = serverValue; + clientObject.value = clientValue; + + // hook should change it on client + int baseCallCount = 0; + clientObject.BaseHookCalled += (oldValue, newValue) => + { + baseCallCount++; + Assert.That(oldValue, Is.EqualTo(clientValue)); + Assert.That(newValue, Is.EqualTo(serverValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(baseCallCount, Is.EqualTo(1)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void VirtualOverrideHook_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out VirtualOverrideHook serverObject, + out _, out _, out VirtualOverrideHook clientObject); + + const int serverValue = 24; + + // change it on server + serverObject.value = serverValue; + + // hook should change it on client + int overrideCallCount = 0; + int baseCallCount = 0; + clientObject.OverrideHookCalled += (oldValue, newValue) => + { + overrideCallCount++; + Assert.That(oldValue, Is.EqualTo(0)); + Assert.That(newValue, Is.EqualTo(serverValue)); + }; + clientObject.BaseHookCalled += (oldValue, newValue) => + { + baseCallCount++; + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(overrideCallCount, Is.EqualTo(1)); + Assert.That(baseCallCount, Is.EqualTo(0)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void AbstractHook_HookCalledWhenSyncingChangedValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out AbstractHook serverObject, + out _, out _, out AbstractHook clientObject); + + const int clientValue = 10; + const int serverValue = 24; + + // change it on server + serverObject.value = serverValue; + clientObject.value = clientValue; + + // hook should change it on client + int callCount = 0; + clientObject.HookCalled += (oldValue, newValue) => + { + callCount++; + Assert.That(oldValue, Is.EqualTo(clientValue)); + Assert.That(newValue, Is.EqualTo(serverValue)); + }; + + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + Assert.That(callCount, Is.EqualTo(1)); + } + + // test to prevent the SyncVar Weaver bug that imer found. + // https://github.com/vis2k/Mirror/pull/2957#issuecomment-1019692366 + // when loading "n = MySyncVar.n", 'ldfdla' loads 'MySyncVar'. + // if we replace MySyncVar with a weaved version like for SyncVar, + // then ldflda for cases like "n = MySyncVar.n" needs to be replaced too. + // + // this wasn't necessary for the original SyncVars, which is why the bug + // wasn't caught by a unit test to begin with. + [Test] + [TestCase(true)] + [TestCase(false)] + public void ImerHook_Ldflda_Uses_Correct_SyncVar(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out ImerHook_Ldflda serverObject, + out _, out _, out ImerHook_Ldflda clientObject); + + // change it on server + serverObject._syncProportions = new Proportions{Array = new byte[]{3, 4}}; + + // sync to client + bool written = SyncToClient(serverObject, clientObject, intialState); + Assert.IsTrue(written); + + // client uses ldflda to get replacement.Array. + // we synced an array with two values, so if ldflda uses the correct + // SyncVar then it shouldn't be null anymore now. + Assert.That(clientObject.ldflda_Array, !Is.Null); + } + + // repro for the bug found by David_548219 in discord where setting + // MyStruct.value would throw invalid IL. + // could happen if we change Weaver [SyncVar] logic / replacements. + // testing syncVar = X isn't enough. + // we should have a test for syncVar.value = X too. + [Test] + [TestCase(true)] + [TestCase(false)] + public void DavidHook_SetSyncVarStructsValue(bool intialState) + { + CreateNetworkedAndSpawn( + out _, out _, out DavidHookComponent serverObject, + out _, out _, out DavidHookComponent clientObject); + + // change it on server. + // should not throw. + serverObject.syncvar.Value = 1337; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs.meta new file mode 100644 index 000000000..fbbf021cf --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarAttributeHookTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2e0f51ab2719c99408ae9c0466fdedc7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs b/Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs new file mode 100644 index 000000000..bbaa3b861 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs @@ -0,0 +1,438 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.SyncVarAttributeTests +{ + class MockPlayer : NetworkBehaviour + { + public struct Guild + { + public string name; + } + + [SyncVar] + public Guild guild; + } + + class SyncVarGameObject : NetworkBehaviour + { + [SyncVar] + public GameObject value; + } + class SyncVarNetworkIdentity : NetworkBehaviour + { + [SyncVar] + public NetworkIdentity value; + } + class SyncVarTransform : NetworkBehaviour + { + [SyncVar] + public Transform value; + } + class SyncVarNetworkBehaviour : NetworkBehaviour + { + [SyncVar] + public SyncVarNetworkBehaviour value; + } + class SyncVarAbstractNetworkBehaviour : NetworkBehaviour + { + public abstract class MockMonsterBase : NetworkBehaviour + { + public abstract string GetName(); + } + + public class MockZombie : MockMonsterBase + { + public override string GetName() => "Zombie"; + } + + public class MockWolf : MockMonsterBase + { + public override string GetName() => "Wolf"; + } + + [SyncVar] + public MockMonsterBase monster1; + + [SyncVar] + public MockMonsterBase monster2; + } + + public class SyncVarAttributeTest : SyncVarAttributeTestBase + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // start server & connect client because we need spawn functions + NetworkServer.Listen(1); + + // we are testing server->client syncs. + // so we need truly separted server & client, not host. + ConnectClientBlockingAuthenticatedAndReady(out _); + } + + [Test] + public void TestSettingStruct() + { + CreateNetworked(out _, out _, out MockPlayer player); + + // synchronize immediately + player.syncInterval = 0f; + + Assert.That(player.IsDirty(), Is.False, "First time object should not be dirty"); + + MockPlayer.Guild myGuild = new MockPlayer.Guild + { + name = "Back street boys" + }; + + player.guild = myGuild; + + Assert.That(player.IsDirty(), "Setting struct should mark object as dirty"); + player.ClearAllDirtyBits(); + Assert.That(player.IsDirty(), Is.False, "ClearAllDirtyBits() should clear dirty flag"); + + // clearing the guild should set dirty bit too + player.guild = default; + Assert.That(player.IsDirty(), "Clearing struct should mark object as dirty"); + } + + [Test] + public void TestSyncIntervalAndClearDirtyComponents() + { + CreateNetworked(out _, out _, out MockPlayer player); + player.lastSyncTime = NetworkTime.localTime; + // synchronize immediately + player.syncInterval = 1f; + + player.guild = new MockPlayer.Guild + { + name = "Back street boys" + }; + + Assert.That(player.IsDirty(), Is.False, "Sync interval not met, so not dirty yet"); + + // ClearDirtyComponents should do nothing since syncInterval is not + // elapsed yet + player.netIdentity.ClearDirtyComponentsDirtyBits(); + + // set lastSyncTime far enough back to be ready for syncing + player.lastSyncTime = NetworkTime.localTime - player.syncInterval; + + // should be dirty now + Assert.That(player.IsDirty(), Is.True, "Sync interval met, should be dirty"); + } + + [Test] + public void TestSyncIntervalAndClearAllComponents() + { + CreateNetworked(out _, out _, out MockPlayer player); + player.lastSyncTime = NetworkTime.localTime; + // synchronize immediately + player.syncInterval = 1f; + + player.guild = new MockPlayer.Guild + { + name = "Back street boys" + }; + + Assert.That(player.IsDirty(), Is.False, "Sync interval not met, so not dirty yet"); + + // ClearAllComponents should clear dirty even if syncInterval not + // elapsed yet + player.netIdentity.ClearAllComponentsDirtyBits(); + + // set lastSyncTime far enough back to be ready for syncing + player.lastSyncTime = NetworkTime.localTime - player.syncInterval; + + // should be dirty now + Assert.That(player.IsDirty(), Is.False, "Sync interval met, should still not be dirty"); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncsGameobject(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarGameObject serverObject, + out _, out _, out SyncVarGameObject clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out GameObject serverValue, out _, + out GameObject clientValue, out _); + + serverObject.value = serverValue; + clientObject.value = null; + + bool written = SyncToClient(serverObject, clientObject, initialState); + Assert.IsTrue(written); + Assert.That(clientObject.value, Is.EqualTo(clientValue)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncIdentity(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarNetworkIdentity serverObject, + out _, out _, out SyncVarNetworkIdentity clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverValue, + out _, out NetworkIdentity clientValue); + + serverObject.value = serverValue; + clientObject.value = null; + + bool written = SyncToClient(serverObject, clientObject, initialState); + Assert.IsTrue(written); + Assert.That(clientObject.value, Is.EqualTo(clientValue)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncTransform(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarTransform serverObject, + out _, out _, out SyncVarTransform clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverIdentity, + out _, out NetworkIdentity clientIdentity); + + Transform serverValue = serverIdentity.transform; + Transform clientValue = clientIdentity.transform; + + serverObject.value = serverValue; + clientObject.value = null; + + bool written = SyncToClient(serverObject, clientObject, initialState); + Assert.IsTrue(written); + Assert.That(clientObject.value, Is.EqualTo(clientValue)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncsBehaviour(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarNetworkBehaviour serverObject, + out _, out _, out SyncVarNetworkBehaviour clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out _, out SyncVarNetworkBehaviour serverValue, + out _, out _, out SyncVarNetworkBehaviour clientValue); + + serverObject.value = serverValue; + clientObject.value = null; + + bool written = SyncToClient(serverObject, clientObject, initialState); + Assert.IsTrue(written); + Assert.That(clientObject.value, Is.EqualTo(clientValue)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncsMultipleBehaviour(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarNetworkBehaviour serverObject, + out _, out _, out SyncVarNetworkBehaviour clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverIdentity, out SyncVarNetworkBehaviour serverBehaviour1, out SyncVarNetworkBehaviour serverBehaviour2, + out _, out NetworkIdentity clientIdentity, out SyncVarNetworkBehaviour clientBehaviour1, out SyncVarNetworkBehaviour clientBehaviour2); + + // create array/set indices + _ = serverIdentity.NetworkBehaviours; + + int index1 = serverBehaviour1.ComponentIndex; + int index2 = serverBehaviour2.ComponentIndex; + + // check components of same type have different indexes + Assert.That(index1, Is.Not.EqualTo(index2)); + + // check behaviour 1 can be synced + serverObject.value = serverBehaviour1; + clientObject.value = null; + + bool written1 = SyncToClient(serverObject, clientObject, initialState); + Assert.IsTrue(written1); + Assert.That(clientObject.value, Is.EqualTo(clientBehaviour1)); + + // check that behaviour 2 can be synced + serverObject.value = serverBehaviour2; + clientObject.value = null; + + bool written2 = SyncToClient(serverObject, clientObject, initialState); + Assert.IsTrue(written2); + Assert.That(clientObject.value, Is.EqualTo(clientBehaviour2)); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncVarCacheNetidForGameObject(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarGameObject serverObject, + out _, out _, out SyncVarGameObject clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out GameObject serverValue, out NetworkIdentity serverIdentity, + out GameObject clientValue, out NetworkIdentity clientIdentity); + + Assert.That(serverValue, Is.Not.Null, "getCreatedValue should not return null"); + + serverObject.value = serverValue; + clientObject.value = null; + + // write server data + bool written = ServerWrite(serverObject, initialState, out ArraySegment data, out int writeLength); + Assert.IsTrue(written, "did not write"); + + // remove identity from client, as if it walked out of range + NetworkClient.spawned.Remove(clientIdentity.netId); + + // read client data, this should be cached in field + ClientRead(clientObject, initialState, data, writeLength); + + // check field shows as null + Assert.That(clientObject.value, Is.EqualTo(null), "field should return null"); + + // add identity back to collection, as if it walked back into range + NetworkClient.spawned.Add(clientIdentity.netId, clientIdentity); + + // check field finds value + Assert.That(clientObject.value, Is.EqualTo(clientValue), "fields should return clientValue"); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncVarCacheNetidForIdentity(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarNetworkIdentity serverObject, + out _, out _, out SyncVarNetworkIdentity clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverValue, + out _, out NetworkIdentity clientValue); + + Assert.That(serverValue, Is.Not.Null, "getCreatedValue should not return null"); + + serverObject.value = serverValue; + clientObject.value = null; + + // write server data + bool written = ServerWrite(serverObject, initialState, out ArraySegment data, out int writeLength); + Assert.IsTrue(written, "did not write"); + + // remove identity from client, as if it walked out of range + NetworkClient.spawned.Remove(clientValue.netId); + + // read client data, this should be cached in field + ClientRead(clientObject, initialState, data, writeLength); + + // check field shows as null + Assert.That(clientObject.value, Is.EqualTo(null), "field should return null"); + + // add identity back to collection, as if it walked back into range + NetworkClient.spawned.Add(clientValue.netId, clientValue); + + // check field finds value + Assert.That(clientObject.value, Is.EqualTo(clientValue), "fields should return clientValue"); + } + + [Test] + [TestCase(true)] + [TestCase(false)] + public void SyncVarCacheNetidForBehaviour(bool initialState) + { + CreateNetworkedAndSpawn( + out _, out _, out SyncVarNetworkBehaviour serverObject, + out _, out _, out SyncVarNetworkBehaviour clientObject); + + // create spawned because we will look up netId in .spawned + CreateNetworkedAndSpawn( + out _, out NetworkIdentity serverIdentity, out SyncVarNetworkBehaviour serverValue, + out _, out NetworkIdentity clientIdentity, out SyncVarNetworkBehaviour clientValue); + + Assert.That(serverValue, Is.Not.Null, "getCreatedValue should not return null"); + + // set on server + serverObject.value = serverValue; + clientObject.value = null; + + // write server data + bool written = ServerWrite(serverObject, initialState, out ArraySegment data, out int writeLength); + Assert.IsTrue(written, "did not write"); + + // remove identity from client, as if it walked out of range + NetworkClient.spawned.Remove(clientIdentity.netId); + + // read client data, this should be cached in field + ClientRead(clientObject, initialState, data, writeLength); + + // check field shows as null + Assert.That(clientObject.value, Is.EqualTo(null), "field should return null"); + + // add identity back to collection, as if it walked back into range + NetworkClient.spawned.Add(clientIdentity.netId, clientIdentity); + + // check field finds value + Assert.That(clientObject.value, Is.EqualTo(clientValue), "fields should return clientValue"); + } + + [Test] + public void TestSyncingAbstractNetworkBehaviour() + { + // set up a "server" object + CreateNetworked(out _, out NetworkIdentity serverIdentity, out SyncVarAbstractNetworkBehaviour serverBehaviour); + + // spawn syncvar targets + CreateNetworked(out _, out NetworkIdentity wolfIdentity, out SyncVarAbstractNetworkBehaviour.MockWolf wolf); + CreateNetworked(out _, out NetworkIdentity zombieIdentity, out SyncVarAbstractNetworkBehaviour.MockZombie zombie); + + wolfIdentity.netId = 135; + zombieIdentity.netId = 246; + + serverBehaviour.monster1 = wolf; + serverBehaviour.monster2 = zombie; + + // serialize all the data as we would for the network + NetworkWriter ownerWriter = new NetworkWriter(); + // not really used in this Test + NetworkWriter observersWriter = new NetworkWriter(); + serverIdentity.OnSerializeAllSafely(true, ownerWriter, observersWriter); + + // set up a "client" object + CreateNetworked(out _, out NetworkIdentity clientIdentity, out SyncVarAbstractNetworkBehaviour clientBehaviour); + + // apply all the data from the server object + NetworkReader reader = new NetworkReader(ownerWriter.ToArray()); + clientIdentity.OnDeserializeAllSafely(reader, true); + + // check that the syncvars got updated + Assert.That(clientBehaviour.monster1, Is.EqualTo(serverBehaviour.monster1), "Data should be synchronized"); + Assert.That(clientBehaviour.monster2, Is.EqualTo(serverBehaviour.monster2), "Data should be synchronized"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs.meta new file mode 100644 index 000000000..3bfd55f15 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarAttributeTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7a1a87ad2c7e74dc69138ba36f583640 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs b/Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs new file mode 100644 index 000000000..033b2e25f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs @@ -0,0 +1,37 @@ +using System; +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class SyncVarAttributeTestBase : MirrorEditModeTest + { + // returns If data was written by OnSerialize + public static bool SyncToClient(T serverObject, T clientObject, bool initialState) where T : NetworkBehaviour + { + bool written = ServerWrite(serverObject, initialState, out ArraySegment data, out int writeLength); + ClientRead(clientObject, initialState, data, writeLength); + return written; + } + + public static bool ServerWrite(T serverObject, bool initialState, out ArraySegment data, out int writeLength) where T : NetworkBehaviour + { + NetworkWriter writer = new NetworkWriter(); + bool written = serverObject.OnSerialize(writer, initialState); + writeLength = writer.Position; + data = writer.ToArraySegment(); + return written; + } + + public static void ClientRead(T clientObject, bool initialState, ArraySegment data, int writeLength) where T : NetworkBehaviour + { + NetworkReader reader = new NetworkReader(data); + clientObject.OnDeserialize(reader, initialState); + + int readLength = reader.Position; + Assert.That(writeLength == readLength, + $"OnSerializeAll and OnDeserializeAll calls write the same amount of data\n" + + $" writeLength={writeLength}\n" + + $" readLength={readLength}"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs.meta new file mode 100644 index 000000000..63af154ed --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarAttributeTestBase.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6fbd8fff4a0795d49a0b122554ed6b13 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs b/Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs new file mode 100644 index 000000000..9224fbb7e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs @@ -0,0 +1,169 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class SyncVarGameObjectTests : MirrorTest + { + GameObject go; + NetworkIdentity identity; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // need a connected client & server so we can have spawned identities + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // need a spawned GameObject with a netId (we store by netId) + CreateNetworkedAndSpawn(out go, out identity); + Assert.That(identity.netId, !Is.EqualTo(0)); + } + + [TearDown] + public override void TearDown() => base.TearDown(); + + // make sure the GameObject ctor works, even though base is uint + [Test] + public void Constructor_GameObject() + { + SyncVarGameObject field = new SyncVarGameObject(go); + Assert.That(field.Value, Is.EqualTo(go)); + } + + // make sure the GameObject .Value works, even though base is uint + [Test] + public void Value_GameObject() + { + SyncVarGameObject field = new SyncVarGameObject(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = go; + Assert.That(field.Value, Is.EqualTo(go)); + } + + [Test] + public void ImplicitTo() + { + SyncVarGameObject field = new SyncVarGameObject(go); + // T = field implicit conversion should get .Value + GameObject value = field; + Assert.That(value, Is.EqualTo(go)); + } + + [Test] + public void ImplicitFrom_SetsValue() + { + // field = T implicit conversion should set .Value + SyncVarGameObject field = go; + Assert.That(field.Value, Is.EqualTo(go)); + } + + // make sure the GameObject hook works, even though base is uint. + [Test] + public void Hook() + { + int called = 0; + void OnChanged(GameObject oldValue, GameObject newValue) + { + ++called; + Assert.That(oldValue, Is.Null); + Assert.That(newValue, Is.EqualTo(go)); + } + + SyncVarGameObject field = new SyncVarGameObject(null, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = go; + Assert.That(called, Is.EqualTo(1)); + } + + // SyncField should check .Value for equality. + // two syncfields with same GameObject should be equal. + [Test] + public void EqualsTest() + { + SyncVarGameObject fieldA = new SyncVarGameObject(go); + SyncVarGameObject fieldB = new SyncVarGameObject(go); + SyncVarGameObject fieldC = new SyncVarGameObject(null); + Assert.That(fieldA.Equals(fieldB), Is.True); + Assert.That(fieldA.Equals(fieldC), Is.False); + } + + [Test] + public void PersistenceThroughDisappearance() + { + // field with identity + SyncVarGameObject field = new SyncVarGameObject(go); + + // remove from spawned, shouldn't be found anymore + NetworkServer.spawned.Remove(identity.netId); + Assert.That(field.Value, Is.EqualTo(null)); + + // add to spawned again + // add to spawned again, should be found again + NetworkServer.spawned[identity.netId] = identity; + Assert.That(field.Value, Is.EqualTo(go)); + } + + [Test] + public void SerializeAllWritesNetId() + { + SyncVarGameObject field = new SyncVarGameObject(go); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeAll(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(identity.netId)); + } + + [Test] + public void SerializeDeltaWritesNetId() + { + SyncVarGameObject field = new SyncVarGameObject(go); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeDelta(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(identity.netId)); + } + + [Test] + public void DeserializeAllReadsNetId() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(identity.netId); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarGameObject field = new SyncVarGameObject(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeAll(reader); + Assert.That(field.Value, Is.EqualTo(go)); + } + + [Test] + public void DeserializeDeltaReadsNetId() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(identity.netId); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarGameObject field = new SyncVarGameObject(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeDelta(reader); + Assert.That(field.Value, Is.EqualTo(go)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs.meta new file mode 100644 index 000000000..67956ffa4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarGameObjectTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e09fed54f2b64402928a287b2fd8313f +timeCreated: 1632232605 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs new file mode 100644 index 000000000..3698796da --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs @@ -0,0 +1,190 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + // SyncVarNetworkBehaviour for a abstract NetworkBehaviour + public class SyncVarNetworkBehaviourAbstractTests : MirrorTest + { + NetworkIdentity identity; + NetworkBehaviour component; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // need a connected client & server so we can have spawned identities + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // need a spawned NetworkIdentity with a netId (we store by netId) + // we need a valid NetworkBehaviour component. + // the class is abstract so we need to use EmptyBehaviour and cast back + CreateNetworkedAndSpawn(out _, out identity, out EmptyBehaviour inheritedComponent); + component = inheritedComponent; + Assert.That(identity.netId, !Is.EqualTo(0)); + } + + [TearDown] + public override void TearDown() => base.TearDown(); + + [Test] + public void Pack() + { + ulong packed = SyncVarNetworkBehaviour.Pack(0xAABBCCDD, 0x12); + Assert.That(packed, Is.EqualTo(0xAABBCCDD00000012)); + } + + [Test] + public void Unpack() + { + SyncVarNetworkBehaviour.Unpack(0xAABBCCDD00000012, out uint netId, out byte componentIndex); + Assert.That(netId, Is.EqualTo(0xAABBCCDD)); + Assert.That(componentIndex, Is.EqualTo(0x12)); + } + + // make sure the NetworkBehaviour ctor works, even though base is uint + [Test] + public void Constructor_NetworkBehaviour() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + Assert.That(field.Value, Is.EqualTo(component)); + } + + // make sure the NetworkBehaviour .Value works, even though base is uint + [Test] + public void Value_NetworkBehaviour() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = component; + Assert.That(field.Value, Is.EqualTo(component)); + } + + [Test] + public void PersistenceThroughDisappearance() + { + // field with NetworkBehaviour + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + + // remove from spawned, shouldn't be found anymore + NetworkServer.spawned.Remove(identity.netId); + Assert.That(field.Value, Is.EqualTo(null)); + + // add to spawned again, should be found again + NetworkServer.spawned[identity.netId] = identity; + Assert.That(field.Value, Is.EqualTo(component)); + } + + [Test] + public void ImplicitTo() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + // T = field implicit conversion should get .Value + NetworkBehaviour value = field; + Assert.That(value, Is.EqualTo(component)); + } + + [Test] + public void ImplicitFrom_SetsValue() + { + // field = T implicit conversion should set .Value + SyncVarNetworkBehaviour field = component; + Assert.That(field.Value, Is.EqualTo(component)); + } + + // make sure the NetworkBehaviour hook works, even though base is uint. + [Test] + public void Hook() + { + int called = 0; + void OnChanged(NetworkBehaviour oldValue, NetworkBehaviour newValue) + { + ++called; + Assert.That(oldValue, Is.Null); + Assert.That(newValue, Is.EqualTo(component)); + } + + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = component; + Assert.That(called, Is.EqualTo(1)); + } + + // SyncField should check .Value for equality. + // two syncfields with same NetworkBehaviour should be equal. + [Test] + public void EqualsTest() + { + SyncVarNetworkBehaviour fieldA = new SyncVarNetworkBehaviour(component); + SyncVarNetworkBehaviour fieldB = new SyncVarNetworkBehaviour(component); + SyncVarNetworkBehaviour fieldC = new SyncVarNetworkBehaviour(null); + Assert.That(fieldA.Equals(fieldB), Is.True); + Assert.That(fieldA.Equals(fieldC), Is.False); + } + + [Test] + public void SerializeAllWritesNetIdAndComponentIndex() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeAll(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(component.netId)); + Assert.That(reader.ReadByte(), Is.EqualTo(component.ComponentIndex)); + } + + [Test] + public void SerializeDeltaWritesNetIdAndComponentIndex() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeDelta(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(component.netId)); + Assert.That(reader.ReadByte(), Is.EqualTo(component.ComponentIndex)); + } + + [Test] + public void DeserializeAllReadsNetIdAndComponentIndex() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(component.netId); + writer.WriteByte((byte)component.ComponentIndex); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeAll(reader); + Assert.That(field.Value, Is.EqualTo(component)); + } + + [Test] + public void DeserializeDeltaReadsNetIdAndComponentIndex() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(component.netId); + writer.WriteByte((byte)component.ComponentIndex); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeDelta(reader); + Assert.That(field.Value, Is.EqualTo(component)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs.meta new file mode 100644 index 000000000..6f23f9237 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourAbstractTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 3149b1d08c904a0092a504b4345afbd1 +timeCreated: 1633753766 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs new file mode 100644 index 000000000..bf04a90ba --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs @@ -0,0 +1,187 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + // SyncVarNetworkBehaviour for a class that inherits from NetworkBehaviour + public class SyncVarNetworkBehaviourInheritedTests : MirrorTest + { + NetworkIdentity identity; + EmptyBehaviour component; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // need a connected client & server so we can have spawned identities + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // need a spawned NetworkIdentity with a netId (we store by netId) + CreateNetworkedAndSpawn(out _, out identity, out component); + Assert.That(identity.netId, !Is.EqualTo(0)); + } + + [TearDown] + public override void TearDown() => base.TearDown(); + + [Test] + public void Pack() + { + ulong packed = SyncVarNetworkBehaviour.Pack(0xAABBCCDD, 0x12); + Assert.That(packed, Is.EqualTo(0xAABBCCDD00000012)); + } + + [Test] + public void Unpack() + { + SyncVarNetworkBehaviour.Unpack(0xAABBCCDD00000012, out uint netId, out byte componentIndex); + Assert.That(netId, Is.EqualTo(0xAABBCCDD)); + Assert.That(componentIndex, Is.EqualTo(0x12)); + } + + // make sure the NetworkBehaviour ctor works, even though base is uint + [Test] + public void Constructor_NetworkBehaviour() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + Assert.That(field.Value, Is.EqualTo(component)); + } + + // make sure the NetworkBehaviour .Value works, even though base is uint + [Test] + public void Value_NetworkBehaviour() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = component; + Assert.That(field.Value, Is.EqualTo(component)); + } + + [Test] + public void PersistenceThroughDisappearance() + { + // field with NetworkBehaviour + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + + // remove from spawned, shouldn't be found anymore + NetworkServer.spawned.Remove(identity.netId); + Assert.That(field.Value, Is.EqualTo(null)); + + // add to spawned again, should be found again + NetworkServer.spawned[identity.netId] = identity; + Assert.That(field.Value, Is.EqualTo(component)); + } + + [Test] + public void ImplicitTo() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + // T = field implicit conversion should get .Value + EmptyBehaviour value = field; + Assert.That(value, Is.EqualTo(component)); + } + + [Test] + public void ImplicitFrom_SetsValue() + { + // field = T implicit conversion should set .Value + SyncVarNetworkBehaviour field = component; + Assert.That(field.Value, Is.EqualTo(component)); + } + + // make sure the NetworkBehaviour hook works, even though base is uint. + [Test] + public void Hook() + { + int called = 0; + void OnChanged(EmptyBehaviour oldValue, EmptyBehaviour newValue) + { + ++called; + Assert.That(oldValue, Is.Null); + Assert.That(newValue, Is.EqualTo(component)); + } + + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = component; + Assert.That(called, Is.EqualTo(1)); + } + + // SyncField should check .Value for equality. + // two syncfields with same NetworkBehaviour should be equal. + [Test] + public void EqualsTest() + { + SyncVarNetworkBehaviour fieldA = new SyncVarNetworkBehaviour(component); + SyncVarNetworkBehaviour fieldB = new SyncVarNetworkBehaviour(component); + SyncVarNetworkBehaviour fieldC = new SyncVarNetworkBehaviour(null); + Assert.That(fieldA.Equals(fieldB), Is.True); + Assert.That(fieldA.Equals(fieldC), Is.False); + } + + [Test] + public void SerializeAllWritesNetIdAndComponentIndex() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeAll(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(component.netId)); + Assert.That(reader.ReadByte(), Is.EqualTo(component.ComponentIndex)); + } + + [Test] + public void SerializeDeltaWritesNetIdAndComponentIndex() + { + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(component); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeDelta(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(component.netId)); + Assert.That(reader.ReadByte(), Is.EqualTo(component.ComponentIndex)); + } + + [Test] + public void DeserializeAllReadsNetIdAndComponentIndex() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(component.netId); + writer.WriteByte((byte)component.ComponentIndex); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeAll(reader); + Assert.That(field.Value, Is.EqualTo(component)); + } + + [Test] + public void DeserializeDeltaReadsNetIdAndComponentIndex() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(component.netId); + writer.WriteByte((byte)component.ComponentIndex); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarNetworkBehaviour field = new SyncVarNetworkBehaviour(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeDelta(reader); + Assert.That(field.Value, Is.EqualTo(component)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs.meta new file mode 100644 index 000000000..67bbf683c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarNetworkBehaviourInheritedTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 14b1e10f8534435aa611ea329253081b +timeCreated: 1632367114 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs b/Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs new file mode 100644 index 000000000..4e9304083 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs @@ -0,0 +1,166 @@ +using NUnit.Framework; + +namespace Mirror.Tests +{ + public class SyncVarNetworkIdentityTests : MirrorTest + { + NetworkIdentity identity; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // need a connected client & server so we can have spawned identities + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + + // need a spawned NetworkIdentity with a netId (we store by netId) + CreateNetworkedAndSpawn(out _, out identity); + Assert.That(identity.netId, !Is.EqualTo(0)); + } + + [TearDown] + public override void TearDown() => base.TearDown(); + + // make sure the NetworkIdentity ctor works, even though base is uint + [Test] + public void Constructor_NetworkIdentity() + { + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(identity); + Assert.That(field.Value, Is.EqualTo(identity)); + } + + // make sure the NetworkIdentity .Value works, even though base is uint + [Test] + public void Value_NetworkIdentity() + { + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = identity; + Assert.That(field.Value, Is.EqualTo(identity)); + } + + [Test] + public void PersistenceThroughDisappearance() + { + // field with identity + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(identity); + + // remove from spawned, shouldn't be found anymore + NetworkServer.spawned.Remove(identity.netId); + Assert.That(field.Value, Is.EqualTo(null)); + + // add to spawned again, should be found again + NetworkServer.spawned[identity.netId] = identity; + Assert.That(field.Value, Is.EqualTo(identity)); + } + + [Test] + public void ImplicitTo() + { + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(identity); + // T = field implicit conversion should get .Value + NetworkIdentity value = field; + Assert.That(value, Is.EqualTo(identity)); + } + + [Test] + public void ImplicitFrom_SetsValue() + { + // field = T implicit conversion should set .Value + SyncVarNetworkIdentity field = identity; + Assert.That(field.Value, Is.EqualTo(identity)); + } + + // make sure the NetworkIdentity hook works, even though base is uint. + [Test] + public void Hook() + { + int called = 0; + void OnChanged(NetworkIdentity oldValue, NetworkIdentity newValue) + { + ++called; + Assert.That(oldValue, Is.Null); + Assert.That(newValue, Is.EqualTo(identity)); + } + + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(null, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = identity; + Assert.That(called, Is.EqualTo(1)); + } + + // SyncField should check .Value for equality. + // two syncfields with same NetworkIdentity should be equal. + [Test] + public void EqualsTest() + { + SyncVarNetworkIdentity fieldA = new SyncVarNetworkIdentity(identity); + SyncVarNetworkIdentity fieldB = new SyncVarNetworkIdentity(identity); + SyncVarNetworkIdentity fieldC = new SyncVarNetworkIdentity(null); + Assert.That(fieldA.Equals(fieldB), Is.True); + Assert.That(fieldA.Equals(fieldC), Is.False); + } + + [Test] + public void SerializeAllWritesNetId() + { + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(identity); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeAll(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(identity.netId)); + } + + [Test] + public void SerializeDeltaWritesNetId() + { + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(identity); + NetworkWriter writer = new NetworkWriter(); + field.OnSerializeDelta(writer); + + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + Assert.That(reader.ReadUInt(), Is.EqualTo(identity.netId)); + } + + [Test] + public void DeserializeAllReadsNetId() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(identity.netId); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeAll(reader); + Assert.That(field.Value, Is.EqualTo(identity)); + } + + [Test] + public void DeserializeDeltaReadsNetId() + { + NetworkWriter writer = new NetworkWriter(); + writer.WriteUInt(identity.netId); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + SyncVarNetworkIdentity field = new SyncVarNetworkIdentity(null); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.OnDeserializeDelta(reader); + Assert.That(field.Value, Is.EqualTo(identity)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs.meta new file mode 100644 index 000000000..7afbf3314 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarNetworkIdentityTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 064d7f97eae04da095f6b1ec23e66c2b +timeCreated: 1632209446 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/SyncVarTests.cs b/Assets/Mirror/Tests/Editor/SyncVarTests.cs new file mode 100644 index 000000000..f8821579d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarTests.cs @@ -0,0 +1,254 @@ +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests +{ + public class SyncVarTests : MirrorTest + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // SyncVar hooks are only called while client is active for now. + // so we need an active client. + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + + [TearDown] + public override void TearDown() + { + base.TearDown(); + } + + // SyncField should recommend SyncFielGameObject instead + [Test] + public void SyncFieldGameObject_Recommendation() + { + // should show even if value is null since T is + LogAssert.Expect(LogType.Warning, new Regex($"Use explicit {nameof(SyncVarGameObject)}.*")); + SyncVar _ = new SyncVar(null); + } + + // SyncField should recommend SyncFielNetworkIdentity instead + [Test] + public void SyncFieldNetworkIdentity_Recommendation() + { + // should show even if value is null since T is + LogAssert.Expect(LogType.Warning, new Regex($"Use explicit {nameof(SyncVarNetworkIdentity)}.*")); + SyncVar _ = new SyncVar(null); + } + + // SyncField should recommend SyncFielNetworkBehaviour instead + [Test] + public void SyncFieldNetworkBehaviour_Recommendation() + { + // should show even if value is null since T is + LogAssert.Expect(LogType.Warning, new Regex($"Use explicit SyncVarNetworkBehaviour.*")); + SyncVar _ = new SyncVar(null); + } + + [Test] + public void SetValue_SetsValue() + { + // .Value is a property which does several things. + // make sure it .set actually sets the value + SyncVar field = 42; + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = 1337; + Assert.That(field.Value, Is.EqualTo(1337)); + } + + [Test] + public void SetValue_CallsOnDirty() + { + SyncVar field = 42; + int dirtyCalled = 0; + field.OnDirty = () => ++dirtyCalled; + + // setting SyncField.Value should call dirty + field.Value = 1337; + Assert.That(dirtyCalled, Is.EqualTo(1)); + } + + [Test] + public void SetValue_CallsOnDirty_OnlyIfValueChanged() + { + SyncVar field = 42; + int dirtyCalled = 0; + field.OnDirty = () => ++dirtyCalled; + + // setting same value should not call OnDirty again + field.Value = 42; + Assert.That(dirtyCalled, Is.EqualTo(0)); + } + + [Test] + public void ImplicitTo() + { + SyncVar field = new SyncVar(42); + // T = field implicit conversion should get .Value + int value = field; + Assert.That(value, Is.EqualTo(42)); + } + + [Test] + public void ImplicitFrom_SetsValue() + { + // field = T implicit conversion should set .Value + SyncVar field = 42; + Assert.That(field.Value, Is.EqualTo(42)); + } + + [Test] + public void Hook_IsCalled() + { + int called = 0; + void OnChanged(int oldValue, int newValue) + { + ++called; + Assert.That(oldValue, Is.EqualTo(42)); + Assert.That(newValue, Is.EqualTo(1337)); + } + + SyncVar field = new SyncVar(42, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + field.Value = 1337; + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void Hook_OnlyCalledIfValueChanged() + { + int called = 0; + void OnChanged(int oldValue, int newValue) + { + ++called; + Assert.That(oldValue, Is.EqualTo(42)); + Assert.That(newValue, Is.EqualTo(1337)); + } + + SyncVar field = new SyncVar(42, OnChanged); + // assign same value again. hook shouldn't be called again. + field.Value = 42; + Assert.That(called, Is.EqualTo(0)); + } + + [Test] + public void Hook_Set_DoesntDeadlock() + { + // Value.set calls the hook. + // calling Value.set inside the hook would deadlock. + // this needs to be prevented. + SyncVar field = null; + int called = 0; + void OnChanged(int oldValue, int newValue) + { + // setting a different value calls setter -> hook again + field.Value = 0; + ++called; + } + field = new SyncVar(42, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + // setting a different value will call the hook + field.Value = 1337; + // in the end, hook should've been called exactly once + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void DeserializeAll_CallsHook() + { + // create field with hook + int called = 0; + void OnChanged(int oldValue, int newValue) + { + ++called; + Assert.That(oldValue, Is.EqualTo(42)); + Assert.That(newValue, Is.EqualTo(1337)); + } + SyncVar field = new SyncVar(42, OnChanged); + + // avoid 'not initialized' exception + field.OnDirty = () => {}; + + // create reader with data + NetworkWriter writer = new NetworkWriter(); + writer.WriteInt(1337); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + // deserialize + field.OnDeserializeAll(reader); + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void DeserializeDelta_CallsHook() + { + // create field with hook + int called = 0; + void OnChanged(int oldValue, int newValue) + { + ++called; + Assert.That(oldValue, Is.EqualTo(42)); + Assert.That(newValue, Is.EqualTo(1337)); + } + SyncVar fieldWithHook = new SyncVar(42, OnChanged); + + // avoid 'not initialized' exception + fieldWithHook.OnDirty = () => {}; + + // create reader with data + NetworkWriter writer = new NetworkWriter(); + writer.WriteInt(1337); + NetworkReader reader = new NetworkReader(writer.ToArraySegment()); + + // deserialize + fieldWithHook.OnDeserializeDelta(reader); + Assert.That(called, Is.EqualTo(1)); + } + + [Test] + public void EqualsT() + { + // .Equals should compare .Value + SyncVar field = 42; + Assert.That(field.Equals(42), Is.True); + } + + [Test] + public void EqualsNull() + { + // .Equals(null) should always be false. so that == null works. + SyncVar field = 42; + Assert.That(field.Equals(null), Is.False); + } + + [Test] + public void EqualsEqualsT() + { + // == should compare .Value + SyncVar field = 42; + Assert.That(field == 42, Is.True); + } + + [Test] + public void ToString_CallsValueToString() + { + SyncVar field = 42; + Assert.That(field.ToString(), Is.EqualTo("42")); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/SyncVarTests.cs.meta b/Assets/Mirror/Tests/Editor/SyncVarTests.cs.meta new file mode 100644 index 000000000..078a619e5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/SyncVarTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e3e4fde08fe240f79ad665b57d2e71c2 +timeCreated: 1632056463 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs b/Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs new file mode 100644 index 000000000..f4e565acf --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs @@ -0,0 +1,138 @@ +using System; +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class VirtualTargetRpc : NetworkBehaviour + { + public event Action onVirtualSendInt; + + [TargetRpc] + public virtual void TargetSendInt(int someInt) + { + onVirtualSendInt?.Invoke(someInt); + } + } + + class VirtualNoOverrideTargetRpc : VirtualTargetRpc {} + + class VirtualOverrideTargetRpc : VirtualTargetRpc + { + public event Action onOverrideSendInt; + + [TargetRpc] + public override void TargetSendInt(int someInt) + { + onOverrideSendInt?.Invoke(someInt); + } + } + + class VirtualOverrideTargetRpcWithBase : VirtualTargetRpc + { + public event Action onOverrideSendInt; + + [TargetRpc] + public override void TargetSendInt(int someInt) + { + base.TargetSendInt(someInt); + onOverrideSendInt?.Invoke(someInt); + } + } + + public class TargetRpcOverrideTest : RemoteTestBase + { + [Test] + public void VirtualRpcIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualTargetRpc hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.TargetSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + } + + [Test] + public void VirtualCommandWithNoOverrideIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualNoOverrideTargetRpc hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.TargetSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualRpcIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideTargetRpc hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.TargetSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(0)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + } + + [Test] + public void OverrideVirtualWithBaseCallsBothVirtualAndBase() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out VirtualOverrideTargetRpcWithBase hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int virtualCallCount = 0; + int overrideCallCount = 0; + hostBehaviour.onVirtualSendInt += incomingInt => + { + virtualCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.onOverrideSendInt += incomingInt => + { + overrideCallCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + + hostBehaviour.TargetSendInt(someInt); + ProcessMessages(); + Assert.That(virtualCallCount, Is.EqualTo(1)); + Assert.That(overrideCallCount, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs.meta b/Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs.meta new file mode 100644 index 000000000..8cb1e07da --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TargetRpcOverrideTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 56e76b5f8b5fe2d40ade963d179ef76e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/TargetRpcTest.cs b/Assets/Mirror/Tests/Editor/TargetRpcTest.cs new file mode 100644 index 000000000..b85fc9858 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TargetRpcTest.cs @@ -0,0 +1,181 @@ +using System; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.RemoteAttrributeTest +{ + class TargetRpcBehaviour : NetworkBehaviour + { + public event Action onSendInt; + + [TargetRpc] + public void SendInt(int someInt) + { + onSendInt?.Invoke(someInt); + } + + [TargetRpc] + public void SendIntWithTarget(NetworkConnection target, int someInt) + { + onSendInt?.Invoke(someInt); + } + } + + class TargetRpcOverloads : NetworkBehaviour + { + public int firstCalled = 0; + public int secondCalled = 0; + + [TargetRpc] + public void TargetRpcTest(int _) => ++firstCalled; + + [TargetRpc] + public void TargetRpcTest(string _) => ++secondCalled; + } + + public class TargetRpcTest : RemoteTestBase + { + [Test] + public void TargetRpcIsCalled() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour, NetworkServer.localConnection); + + const int someInt = 20; + + int callCount = 0; + hostBehaviour.onSendInt += incomingInt => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.SendInt(someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void TargetRpcIsCalledOnTarget() + { + // spawn without owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour); + + const int someInt = 20; + + int callCount = 0; + hostBehaviour.onSendInt += incomingInt => + { + callCount++; + Assert.That(incomingInt, Is.EqualTo(someInt)); + }; + hostBehaviour.SendIntWithTarget(NetworkServer.localConnection, someInt); + ProcessMessages(); + Assert.That(callCount, Is.EqualTo(1)); + } + + [Test] + public void ErrorForTargetRpcWithNoOwner() + { + // spawn without owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour); + + const int someInt = 20; + + hostBehaviour.onSendInt += incomingInt => + { + Assert.Fail("Event should not be invoked with error"); + }; + LogAssert.Expect(LogType.Error, $"TargetRPC System.Void Mirror.Tests.RemoteAttrributeTest.TargetRpcBehaviour::SendInt(System.Int32) was given a null connection, make sure the object has an owner or you pass in the target connection"); + hostBehaviour.SendInt(someInt); + } + + [Test] + public void ErrorForTargetRpcWithNullArgment() + { + // spawn without owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour); + + const int someInt = 20; + + hostBehaviour.onSendInt += incomingInt => + { + Assert.Fail("Event should not be invoked with error"); + }; + LogAssert.Expect(LogType.Error, $"TargetRPC System.Void Mirror.Tests.RemoteAttrributeTest.TargetRpcBehaviour::SendIntWithTarget(Mirror.NetworkConnection,System.Int32) was given a null connection, make sure the object has an owner or you pass in the target connection"); + hostBehaviour.SendIntWithTarget(null, someInt); + } + + [Test] + public void ErrorForTargetRpcWhenNotGivenConnectionToClient() + { + // spawn without owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour); + + const int someInt = 20; + + hostBehaviour.onSendInt += incomingInt => + { + Assert.Fail("Event should not be invoked with error"); + }; + LogAssert.Expect(LogType.Error, $"TargetRPC System.Void Mirror.Tests.RemoteAttrributeTest.TargetRpcBehaviour::SendIntWithTarget(Mirror.NetworkConnection,System.Int32) requires a NetworkConnectionToClient but was given {typeof(FakeConnection).Name}"); + hostBehaviour.SendIntWithTarget(new FakeConnection(), someInt); + } + class FakeConnection : NetworkConnection + { + public override string address => throw new NotImplementedException(); + public override void Disconnect() => throw new NotImplementedException(); + internal override void Send(ArraySegment segment, int channelId = 0) => throw new NotImplementedException(); + protected override void SendToTransport(ArraySegment segment, int channelId = Channels.Reliable) => throw new NotImplementedException(); + } + + [Test] + public void ErrorForTargetRpcWhenServerNotActive() + { + // spawn without owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour); + + const int someInt = 20; + + hostBehaviour.onSendInt += incomingInt => + { + Assert.Fail("Event should not be invoked with error"); + }; + NetworkServer.active = false; + LogAssert.Expect(LogType.Error, $"TargetRPC System.Void Mirror.Tests.RemoteAttrributeTest.TargetRpcBehaviour::SendInt(System.Int32) called when server not active"); + hostBehaviour.SendInt(someInt); + } + + [Test] + public void ErrorForTargetRpcWhenObjectNotSpawned() + { + // create without spawning + CreateNetworked(out GameObject _, out NetworkIdentity _, out TargetRpcBehaviour hostBehaviour); + + const int someInt = 20; + + hostBehaviour.onSendInt += incomingInt => + { + Assert.Fail("Event should not be invoked with error"); + }; + LogAssert.Expect(LogType.Warning, $"TargetRpc System.Void Mirror.Tests.RemoteAttrributeTest.TargetRpcBehaviour::SendInt(System.Int32) called on {hostBehaviour.name} but that object has not been spawned or has been unspawned"); + hostBehaviour.SendInt(someInt); + } + + // RemoteCalls uses md.FullName which gives us the full command/rpc name + // like "System.Void Mirror.Tests.RemoteAttrributeTest.AuthorityBehaviour::SendInt(System.Int32)" + // which means overloads with same name but different types should work. + [Test] + public void TargetRpcOverload() + { + // spawn with owner + CreateNetworkedAndSpawn(out GameObject _, out NetworkIdentity _, out TargetRpcOverloads hostBehaviour, NetworkServer.localConnection); + + hostBehaviour.TargetRpcTest(42); + hostBehaviour.TargetRpcTest("A"); + ProcessMessages(); + Assert.That(hostBehaviour.firstCalled, Is.EqualTo(1)); + Assert.That(hostBehaviour.secondCalled, Is.EqualTo(1)); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/TargetRpcTest.cs.meta b/Assets/Mirror/Tests/Editor/TargetRpcTest.cs.meta new file mode 100644 index 000000000..554ebb23c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TargetRpcTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53f71b8db0a8bac448b45ee86a0d355a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs.meta b/Assets/Mirror/Tests/Editor/TestPrefabs.meta new file mode 100644 index 000000000..b181ac967 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c5e7ba67b40c16049a463d65940445b7 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab b/Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab new file mode 100644 index 000000000..b445e7389 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab @@ -0,0 +1,97 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &3117426950187087154 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 51440471024079650} + - component: {fileID: 5409067437793000088} + m_Layer: 0 + m_Name: PrefabWithChildrenForClientScene + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &51440471024079650 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3117426950187087154} + 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: 6537489145038351880} + m_Father: {fileID: 0} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &5409067437793000088 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3117426950187087154} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + sceneId: 4227710719 + serverOnly: 0 + m_AssetId: + hasSpawned: 0 +--- !u!1 &3264653828050749140 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6537489145038351880} + - component: {fileID: 6548650892574975460} + m_Layer: 0 + m_Name: Child Network Identity + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6537489145038351880 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3264653828050749140} + 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: 51440471024079650} + m_RootOrder: 0 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!114 &6548650892574975460 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 3264653828050749140} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + sceneId: 1033643234 + serverOnly: 0 + m_AssetId: + hasSpawned: 0 diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab.meta b/Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab.meta new file mode 100644 index 000000000..d6d295014 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs/PrefabWithChildrenForClientScene.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: a78e009e3f2dee44e8859516974ede43 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab b/Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab new file mode 100644 index 000000000..38f91abdd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab @@ -0,0 +1,49 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &9072865897540379920 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 1476000898204696246} + - component: {fileID: 1423274632536207763} + m_Layer: 0 + m_Name: ValidPrefabForClientScene + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &1476000898204696246 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9072865897540379920} + 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 &1423274632536207763 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 9072865897540379920} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + sceneId: 1350197626 + serverOnly: 0 + m_AssetId: + hasSpawned: 0 diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab.meta b/Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab.meta new file mode 100644 index 000000000..4f06339ab --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs/ValidPrefabForClientScene.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 33169286da0313d45ab5bfccc6cf3775 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab b/Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab new file mode 100644 index 000000000..7dea950b9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab @@ -0,0 +1,32 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &6662908427314056913 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 4633208118073782447} + m_Layer: 0 + m_Name: invalidPrefabForClientScene + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &4633208118073782447 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 6662908427314056913} + 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} diff --git a/Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab.meta b/Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab.meta new file mode 100644 index 000000000..edf3b10fd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/TestPrefabs/invalidPrefabForClientScene.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 78f0a3f755d35324e959f3ecdd993fb0 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/UtilsTests.cs b/Assets/Mirror/Tests/Editor/UtilsTests.cs new file mode 100644 index 000000000..cda272b2f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/UtilsTests.cs @@ -0,0 +1,31 @@ +using NUnit.Framework; +using UnityEngine; + +namespace Mirror.Tests +{ + public class UtilsTests + { + [Test] + public void GetTrueRandomUInt() + { + uint first = Utils.GetTrueRandomUInt(); + uint second = Utils.GetTrueRandomUInt(); + Assert.That(first, !Is.EqualTo(second)); + } + + [Test] + public void IsPointInScreen() + { + int width = Screen.width; + int height = Screen.height; + Assert.That(Utils.IsPointInScreen(new Vector2(-1, -1)), Is.False); + + Assert.That(Utils.IsPointInScreen(new Vector2(0, 0)), Is.True); + Assert.That(Utils.IsPointInScreen(new Vector2(width / 2, height / 2)), Is.True); + + Assert.That(Utils.IsPointInScreen(new Vector2(width, height / 2)), Is.False); + Assert.That(Utils.IsPointInScreen(new Vector2(width / 2, height)), Is.False); + Assert.That(Utils.IsPointInScreen(new Vector2(width + 1, height + 1)), Is.False); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/UtilsTests.cs.meta b/Assets/Mirror/Tests/Editor/UtilsTests.cs.meta new file mode 100644 index 000000000..fef4a5ef1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/UtilsTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4334d1acdbce04172b3faaa632129ba7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver.meta b/Assets/Mirror/Tests/Editor/Weaver.meta new file mode 100644 index 000000000..700c49534 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 90415d9cedbb1f14795854e529289171 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj b/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj new file mode 100644 index 000000000..5fc5f018b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/.WeaverTests.csproj @@ -0,0 +1,280 @@ + + + netstandard2.0 + _WeaverTests2.csproj + False + Temp~\obj\ + + + true + full + false + Temp~\bin\Debug\ + DEBUG;TRACE;UNITY_5_3_OR_NEWER;UNITY_5_4_OR_NEWER;UNITY_5_5_OR_NEWER;UNITY_5_6_OR_NEWER;UNITY_2017_1_OR_NEWER;UNITY_2017_2_OR_NEWER;UNITY_2017_3_OR_NEWER;UNITY_2017_4_OR_NEWER;UNITY_2018_1_OR_NEWER;UNITY_2018_2_OR_NEWER;UNITY_2018_3_OR_NEWER;UNITY_2018_3_6;UNITY_2018_3;UNITY_2018;PLATFORM_ARCH_64;UNITY_64;UNITY_INCLUDE_TESTS;ENABLE_AUDIO;ENABLE_CACHING;ENABLE_CLOTH;ENABLE_DUCK_TYPING;ENABLE_MICROPHONE;ENABLE_MULTIPLE_DISPLAYS;ENABLE_PHYSICS;ENABLE_SPRITES;ENABLE_GRID;ENABLE_TILEMAP;ENABLE_TERRAIN;ENABLE_TEXTURE_STREAMING;ENABLE_DIRECTOR;ENABLE_UNET;ENABLE_LZMA;ENABLE_UNITYEVENTS;ENABLE_WEBCAM;ENABLE_WWW;ENABLE_CLOUD_SERVICES_COLLAB;ENABLE_CLOUD_SERVICES_COLLAB_SOFTLOCKS;ENABLE_CLOUD_SERVICES_ADS;ENABLE_CLOUD_HUB;ENABLE_CLOUD_PROJECT_ID;ENABLE_CLOUD_SERVICES_USE_WEBREQUEST;ENABLE_CLOUD_SERVICES_UNET;ENABLE_CLOUD_SERVICES_BUILD;ENABLE_CLOUD_LICENSE;ENABLE_EDITOR_HUB;ENABLE_EDITOR_HUB_LICENSE;ENABLE_WEBSOCKET_CLIENT;ENABLE_DIRECTOR_AUDIO;ENABLE_DIRECTOR_TEXTURE;ENABLE_TIMELINE;ENABLE_EDITOR_METRICS;ENABLE_EDITOR_METRICS_CACHING;ENABLE_MANAGED_JOBS;ENABLE_MANAGED_TRANSFORM_JOBS;ENABLE_MANAGED_ANIMATION_JOBS;INCLUDE_DYNAMIC_GI;INCLUDE_GI;ENABLE_MONO_BDWGC;PLATFORM_SUPPORTS_MONO;RENDER_SOFTWARE_CURSOR;INCLUDE_PUBNUB;ENABLE_VIDEO;ENABLE_CUSTOM_RENDER_TEXTURE;ENABLE_LOCALIZATION;PLATFORM_STANDALONE_WIN;PLATFORM_STANDALONE;UNITY_STANDALONE_WIN;UNITY_STANDALONE;ENABLE_SUBSTANCE;ENABLE_RUNTIME_GI;ENABLE_MOVIES;ENABLE_NETWORK;ENABLE_CRUNCH_TEXTURE_COMPRESSION;ENABLE_UNITYWEBREQUEST;ENABLE_CLOUD_SERVICES;ENABLE_CLOUD_SERVICES_ANALYTICS;ENABLE_CLOUD_SERVICES_PURCHASING;ENABLE_CLOUD_SERVICES_CRASH_REPORTING;ENABLE_OUT_OF_PROCESS_CRASH_HANDLER;ENABLE_EVENT_QUEUE;ENABLE_CLUSTER_SYNC;ENABLE_CLUSTERINPUT;ENABLE_VR;ENABLE_AR;ENABLE_WEBSOCKET_HOST;ENABLE_MONO;NET_STANDARD_2_0;ENABLE_PROFILER;UNITY_ASSERTIONS;UNITY_EDITOR;UNITY_EDITOR_64;UNITY_EDITOR_WIN;ENABLE_UNITY_COLLECTIONS_CHECKS;ENABLE_BURST_AOT;UNITY_TEAM_LICENSE;ENABLE_VSTU;MIRROR;MIRROR_1726_OR_NEWER;MIRROR_3_0_OR_NEWER;MIRROR_3_12_OR_NEWER;MIRROR_4_0_OR_NEWER;MIRROR_5_0_OR_NEWER;MIRROR_6_0_OR_NEWER;MIRROR_7_0_OR_NEWER;MIRROR_8_0_OR_NEWER;MIRROR_9_0_OR_NEWER;MIRROR_10_0_OR_NEWER;MIRROR_11_0_OR_NEWER;MIRROR_12_0_OR_NEWER;MIRROR_13_0_OR_NEWER;CSHARP_7_OR_LATER;CSHARP_7_3_OR_NEWER + prompt + 4 + 0169 + False + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + G:/UnityEditors/2018.3.6f1/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll + + + + + G:\UnityEditors\2018.3.6f1\Editor\Data\Managed/UnityEngine/UnityEngine.dll + + + G:\UnityEditors\2018.3.6f1\Editor\Data\Managed/UnityEditor.dll + + + G:\UnityEditors\2018.3.6f1\Editor\Data\Managed\UnityEngine\UnityEngine.TextRenderingModule.dll + + + + + {3AD0CCDA-2B85-9DF3-8D00-95C1C6C53989} + Mirror + + + {90A15647-DD13-BF94-74EC-7CAA0D39EE86} + WeaverTestExtraAssembly + + + + diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly.meta new file mode 100644 index 000000000..0938ea191 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: c7ea1d20d9a008f479d65016372c9af9 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs new file mode 100644 index 000000000..57669332d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs @@ -0,0 +1,14 @@ +namespace Mirror.Weaver.Tests.Extra +{ + public struct ComplexData + { + public AnotherData another; + public float q; + } + public struct AnotherData + { + public float a; + public float b; + public float c; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs.meta new file mode 100644 index 000000000..d388cb7be --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/ComplexClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 48d47c0cf65be8d438e25f7039e50855 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md new file mode 100644 index 000000000..b00c220c4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md @@ -0,0 +1,3 @@ +We need this extra Assembly when testing multiple assembles. + +Weaver is currently unable to use reference to assembles that are not in `CompilationPipeline.GetAssemblies()` \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md.meta new file mode 100644 index 000000000..01a20a1e2 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4020f17b5b2fe4842bea9d1e7e6102d8 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs new file mode 100644 index 000000000..bc8615589 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs @@ -0,0 +1,7 @@ +namespace Mirror.Weaver.Tests.Extra +{ + public struct SomeData + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs.meta new file mode 100644 index 000000000..3e39abd3e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeData.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 502dcd3fe9ec0e04185f2afd167c7aad +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs new file mode 100644 index 000000000..b93368de5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs @@ -0,0 +1,7 @@ +namespace Mirror.Weaver.Tests.Extra +{ + public class SomeDataClass + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs.meta new file mode 100644 index 000000000..cf2961c97 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 13374a84968c7684782a54136703257d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs new file mode 100644 index 000000000..b1fd9316a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs @@ -0,0 +1,12 @@ +namespace Mirror.Weaver.Tests.Extra +{ + public class SomeDataClassWithConstructor + { + public int usefulNumber; + + public SomeDataClassWithConstructor() + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs.meta new file mode 100644 index 000000000..a84b180df --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataClassWithConstructor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1e73b5c53b6d21b47ab1442e5ad2265a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs new file mode 100644 index 000000000..d229527a8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs @@ -0,0 +1,19 @@ +namespace Mirror.Weaver.Tests.Extra +{ + public struct SomeDataWithWriter + { + public int usefulNumber; + } + + public static class ReadWrite + { + public static void WriteSomeData(this NetworkWriter writer, SomeDataWithWriter itemData) + { + writer.WriteInt(itemData.usefulNumber); + } + public static SomeDataWithWriter ReadSomeData(this NetworkReader reader) + { + return new SomeDataWithWriter { usefulNumber = reader.ReadInt() }; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs.meta new file mode 100644 index 000000000..48e7de714 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/SomeDataWithWriter.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 15ed55793bf39a04f9ef337575c3c6e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef new file mode 100644 index 000000000..7098b96e8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef @@ -0,0 +1,16 @@ +{ + "name": "WeaverTestExtraAssembly", + "references": [ + "Mirror" + ], + "optionalUnityReferences": [], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef.meta b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef.meta new file mode 100644 index 000000000..65c2e4b43 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/ExtraAssembly/WeaverTestExtraAssembly.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 4887344a35d890449b35f8b1b0a3355a +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/README.md b/Assets/Mirror/Tests/Editor/Weaver/README.md new file mode 100644 index 000000000..9737cfa05 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/README.md @@ -0,0 +1,15 @@ +There are two types of Weaver tests: +* Success tests where we simply have to guarantee that a class is + weaved without issues. +* Failure tests where we need to make sure certain classes are not + weaved because they aren't allowed to. + +The success tests can be regular C# files. +=> Weaver runs automatically when creating them, so we don't even + need to weave those manually with AssemblyBuilder. +=> There are >100 of those tests. moving them to regular C# + removes a LOT of AssemblyBuilder time. + +The failure tests need to be weaved one at a time. +=> Weaver usually stops weaving after the first error. +=> So we weave them all separately to get all the errors. diff --git a/Assets/Mirror/Tests/Editor/Weaver/README.md.meta b/Assets/Mirror/Tests/Editor/Weaver/README.md.meta new file mode 100644 index 000000000..cfa2c7657 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/README.md.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fe0ea9aa683941dc832caafa91f6a72d +timeCreated: 1643081104 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs new file mode 100644 index 000000000..1f4618a2f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs @@ -0,0 +1,180 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Threading; +using UnityEditor.Compilation; +using UnityEngine; + +namespace Mirror.Weaver.Tests +{ + public class WeaverAssembler : MonoBehaviour + { + static string _outputDirectory; + public static string OutputDirectory + { + get + { + if (string.IsNullOrEmpty(_outputDirectory)) + { + _outputDirectory = EditorHelper.FindPath(); + } + return _outputDirectory; + } + } + public static string OutputFile; + public static HashSet SourceFiles { get; private set; } + public static bool AllowUnsafe; + public static List CompilerMessages { get; private set; } + public static bool CompilerErrors { get; private set; } + public static bool DeleteOutputOnClear; + + // static constructor to initialize static properties + static WeaverAssembler() + { + SourceFiles = new HashSet(); + CompilerMessages = new List(); + } + + // Add a range of source files to compile + public static void AddSourceFiles(string[] sourceFiles) + { + foreach (string src in sourceFiles) + { + SourceFiles.Add(Path.Combine(OutputDirectory, src)); + } + } + + // Delete output dll / pdb / mdb + public static void DeleteOutput() + { + // "x.dll" shortest possible dll name + if (OutputFile.Length < 5) + { + return; + } + + string projPathFile = Path.Combine(OutputDirectory, OutputFile); + + try + { + File.Delete(projPathFile); + } + catch {} + + try + { + File.Delete(Path.ChangeExtension(projPathFile, ".pdb")); + } + catch {} + + try + { + File.Delete(Path.ChangeExtension(projPathFile, ".dll.mdb")); + } + catch {} + } + + // clear all settings except for referenced assemblies (which are cleared with ClearReferences) + public static void Clear() + { + if (DeleteOutputOnClear) + { + DeleteOutput(); + } + + CompilerErrors = false; + OutputFile = ""; + SourceFiles.Clear(); + CompilerMessages.Clear(); + AllowUnsafe = false; + DeleteOutputOnClear = false; + } + + public static void Build(Action OnWarning, Action OnError) + { + AssemblyBuilder assemblyBuilder = new AssemblyBuilder(Path.Combine(OutputDirectory, OutputFile), SourceFiles.ToArray()) + { + // "The type 'MonoBehaviour' is defined in an assembly that is not referenced" + referencesOptions = ReferencesOptions.UseEngineModules + }; + if (AllowUnsafe) + { + assemblyBuilder.compilerOptions.AllowUnsafeCode = true; + } + +#if UNITY_2020_3_OR_NEWER + // Unity automatically invokes ILPostProcessor after + // AssemblyBuilder.Build() (on windows at least. not on mac). + // => .buildFinished() below CompilerMessages would already contain + // the weaver messages, failing tests. + // => SyncVarTests->SyncVarSyncList fails too if ILPP was + // already applied by Unity, and we apply it again. + // + // we need to not run ILPP for WeaverTests assemblies here. + // -> we can't set member variables because Unity creates a new + // ILPP instance internally and invokes it + // -> define is passed through ILPP though, and avoids static state. + assemblyBuilder.additionalDefines = new []{ILPostProcessorHook.IgnoreDefine}; +#endif + + assemblyBuilder.buildFinished += delegate (string assemblyPath, CompilerMessage[] compilerMessages) + { + // CompilerMessages from compiling the original test assembly. + // note that we can see weaver messages here if Unity runs + // ILPostProcessor after AssemblyBuilder.Build(). + // => that's why we pass the ignore define above. + CompilerMessages.AddRange(compilerMessages); + foreach (CompilerMessage cm in compilerMessages) + { + if (cm.type == CompilerMessageType.Error) + { + Debug.LogError($"{cm.file}:{cm.line} -- {cm.message}"); + CompilerErrors = true; + } + } + +#if UNITY_2020_3_OR_NEWER + // on 2018/2019, CompilationFinishedHook weaves after building. + // on 2020, ILPostProcessor weaves after building. + // on windows, it runs after AssemblyBuilder.Build() + // on mac, it does not run after AssemblyBuidler.Build() + // => run it manually in all cases + // => this way we can feed result.Logs to test results too + // NOTE: we could simply call Weaver.Weave() here. + // but let's make all tests run through ILPP. + // just like regular projects would. + // helps catch issues early. + + // copy references from assemblyBuilder's references + List references = new List(); + if (assemblyBuilder.defaultReferences != null) + references.AddRange(assemblyBuilder.defaultReferences); + if (assemblyBuilder.additionalReferences != null) + references.AddRange(assemblyBuilder.additionalReferences); + + // invoke ILPostProcessor with an assembly from file. + // NOTE: code for creating and invoking the ILPostProcessor has + // to be in Weaver.dll where 'CompilationPipeline' is + // available due to name being of form 'Unity.*.CodeGen'. + // => we can't change tests to that Unity.*.CodeGen + // because some tests need to be weaved, but ILPP isn't + // ran on Unity.*.CodeGen assemblies itself. + ILPostProcessorFromFile.ILPostProcessFile(assemblyPath, references.ToArray(), OnWarning, OnError); +#endif + }; + + // Start build of assembly + if (!assemblyBuilder.Build()) + { + Debug.LogError($"Failed to start build of assembly {assemblyBuilder.assemblyPath}"); + return; + } + + while (assemblyBuilder.status != AssemblyBuilderStatus.Finished) + { + Thread.Sleep(10); + } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs.meta new file mode 100644 index 000000000..d41b1b4f2 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverAssembler.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 049ac5abfba3c0943a2694cd502fcc80 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs new file mode 100644 index 000000000..7d59264ea --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs @@ -0,0 +1,28 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverClientRpcTests : WeaverTestsBuildFromTestName + { + [Test] + public void ClientRpcCantBeStatic() + { + HasError("RpcCantBeStatic must not be static", + "System.Void WeaverClientRpcTests.ClientRpcCantBeStatic.ClientRpcCantBeStatic::RpcCantBeStatic()"); + } + + [Test] + public void AbstractClientRpc() + { + HasError("Abstract ClientRpc are currently not supported, use virtual method instead", + "System.Void WeaverClientRpcTests.AbstractClientRpc.AbstractClientRpc::RpcDoSomething()"); + } + + [Test] + public void OverrideAbstractClientRpc() + { + HasError("Abstract ClientRpc are currently not supported, use virtual method instead", + "System.Void WeaverClientRpcTests.OverrideAbstractClientRpc.BaseBehaviour::RpcDoSomething()"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs.meta new file mode 100644 index 000000000..4af734600 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1707821d15f4c5e4eb0446b0d2e4a260 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess.meta new file mode 100644 index 000000000..af15d896b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 4f961f3f925c41f295d7ef17978001fe +timeCreated: 1643081346 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs new file mode 100644 index 000000000..c20bc69b5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientRpcTests.BehaviourCanBeSentInRpc +{ + class BehaviourCanBeSentInRpc : NetworkBehaviour + { + [ClientRpc] + void RpcDoSomething(BehaviourCanBeSentInRpc value) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs.meta new file mode 100644 index 000000000..86a338ed1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/BehaviourCanBeSentInRpc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c93dd180605f644c2b24cde840095dd7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs new file mode 100644 index 000000000..77706bab4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientRpcTests.ClientRpcThatExcludesOwner +{ + class ClientRpcThatExcludesOwner : NetworkBehaviour + { + [ClientRpc(includeOwner = false)] + void RpcDoSomething() + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs.meta new file mode 100644 index 000000000..3b237b58c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcThatExcludesOwner.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d1e7f164257c4ce49acc45f27c9e16e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs new file mode 100644 index 000000000..123d19f1d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientRpcTests.ClientRpcValid +{ + class ClientRpcValid : NetworkBehaviour + { + [ClientRpc] + void RpcThatIsTotallyValid() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs.meta new file mode 100644 index 000000000..58ad82d7e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/ClientRpcValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1b6e0531a6ce04c1e98316b51b1b4fda +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs new file mode 100644 index 000000000..ea8742b6d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs @@ -0,0 +1,23 @@ +using Mirror; + + +namespace WeaverClientRpcTests.OverrideVirtualClientRpc +{ + class OverrideVirtualClientRpc : baseBehaviour + { + [ClientRpc] + protected override void RpcDoSomething() + { + // do something + } + } + + class baseBehaviour : NetworkBehaviour + { + [ClientRpc] + protected virtual void RpcDoSomething() + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs.meta new file mode 100644 index 000000000..1ed894435 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/OverrideVirtualClientRpc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b960c909aead4954a70aebf0427f2d2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs new file mode 100644 index 000000000..089f5e0af --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientRpcTests.VirtualClientRpc +{ + class VirtualCommand : NetworkBehaviour + { + [ClientRpc] + protected virtual void RpcDoSomething() + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs.meta new file mode 100644 index 000000000..1fcc4a797 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests_IsSuccess/VirtualClientRpc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2bed05ae46099420281703e1956ec658 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/AbstractClientRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/AbstractClientRpc.cs new file mode 100644 index 000000000..ca91cc764 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/AbstractClientRpc.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientRpcTests.AbstractClientRpc +{ + abstract class AbstractClientRpc : NetworkBehaviour + { + [ClientRpc] + protected abstract void RpcDoSomething(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/ClientRpcCantBeStatic.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/ClientRpcCantBeStatic.cs new file mode 100644 index 000000000..49aa61fd2 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/ClientRpcCantBeStatic.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientRpcTests.ClientRpcCantBeStatic +{ + class ClientRpcCantBeStatic : NetworkBehaviour + { + [ClientRpc] + static void RpcCantBeStatic() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/OverrideAbstractClientRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/OverrideAbstractClientRpc.cs new file mode 100644 index 000000000..4310277d8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientRpcTests~/OverrideAbstractClientRpc.cs @@ -0,0 +1,20 @@ +using Mirror; + + +namespace WeaverClientRpcTests.OverrideAbstractClientRpc +{ + class OverrideAbstractClientRpc : BaseBehaviour + { + [ClientRpc] + protected override void RpcDoSomething() + { + // do something + } + } + + abstract class BaseBehaviour : NetworkBehaviour + { + [ClientRpc] + protected abstract void RpcDoSomething(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs new file mode 100644 index 000000000..7b318164d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs @@ -0,0 +1,130 @@ +using System.IO; +using System.Linq; +using Mono.CecilX; +using Mono.CecilX.Cil; +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverClientServerAttributeTests : WeaverTestsBuildFromTestName + { + // Debug.Log on WeaverTypes to see the strings + const string NetworkServerGetActive = "System.Boolean Mirror.NetworkServer::get_active()"; + const string NetworkClientGetActive = "System.Boolean Mirror.NetworkClient::get_active()"; + + [Test] + public void NetworkBehaviourServer() + { + IsSuccess(); + CheckAddedCode(NetworkServerGetActive, "WeaverClientServerAttributeTests.NetworkBehaviourServer.NetworkBehaviourServer", "ServerOnlyMethod"); + } + + [Test] + public void ServerAttributeOnVirutalMethod() + { + IsSuccess(); + CheckAddedCode(NetworkServerGetActive, "WeaverClientServerAttributeTests.ServerAttributeOnVirutalMethod.ServerAttributeOnVirutalMethod", "ServerOnlyMethod"); + } + + [Test] + public void ServerAttributeOnAbstractMethod() + { + HasError("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", + "System.Void WeaverClientServerAttributeTests.ServerAttributeOnAbstractMethod.ServerAttributeOnAbstractMethod::ServerOnlyMethod()"); + } + + [Test] + public void ServerAttributeOnOverrideMethod() + { + IsSuccess(); + CheckAddedCode(NetworkServerGetActive, "WeaverClientServerAttributeTests.ServerAttributeOnOverrideMethod.ServerAttributeOnOverrideMethod", "ServerOnlyMethod"); + } + + [Test] + public void NetworkBehaviourClient() + { + IsSuccess(); + CheckAddedCode(NetworkClientGetActive, "WeaverClientServerAttributeTests.NetworkBehaviourClient.NetworkBehaviourClient", "ClientOnlyMethod"); + } + + [Test] + public void ClientAttributeOnVirutalMethod() + { + IsSuccess(); + CheckAddedCode(NetworkClientGetActive, "WeaverClientServerAttributeTests.ClientAttributeOnVirutalMethod.ClientAttributeOnVirutalMethod", "ClientOnlyMethod"); + } + + [Test] + public void ClientAttributeOnAbstractMethod() + { + HasError("Server or Client Attributes can't be added to abstract method. Server and Client Attributes are not inherited so they need to be applied to the override methods instead.", + "System.Void WeaverClientServerAttributeTests.ClientAttributeOnAbstractMethod.ClientAttributeOnAbstractMethod::ClientOnlyMethod()"); + } + + [Test] + public void ClientAttributeOnOverrideMethod() + { + IsSuccess(); + CheckAddedCode(NetworkClientGetActive, "WeaverClientServerAttributeTests.ClientAttributeOnOverrideMethod.ClientAttributeOnOverrideMethod", "ClientOnlyMethod"); + } + + [Test] + public void StaticClassClient() + { + IsSuccess(); + CheckAddedCode(NetworkClientGetActive, "WeaverClientServerAttributeTests.StaticClassClient.StaticClassClient", "ClientOnlyMethod"); + } + + [Test] + public void RegularClassClient() + { + IsSuccess(); + CheckAddedCode(NetworkClientGetActive, "WeaverClientServerAttributeTests.RegularClassClient.RegularClassClient", "ClientOnlyMethod"); + } + + [Test] + public void MonoBehaviourClient() + { + IsSuccess(); + CheckAddedCode(NetworkClientGetActive, "WeaverClientServerAttributeTests.MonoBehaviourClient.MonoBehaviourClient", "ClientOnlyMethod"); + } + + [Test] + public void StaticClassServer() + { + IsSuccess(); + CheckAddedCode(NetworkServerGetActive, "WeaverClientServerAttributeTests.StaticClassServer.StaticClassServer", "ServerOnlyMethod"); + } + + [Test] + public void RegularClassServer() + { + IsSuccess(); + CheckAddedCode(NetworkServerGetActive, "WeaverClientServerAttributeTests.RegularClassServer.RegularClassServer", "ServerOnlyMethod"); + } + + [Test] + public void MonoBehaviourServer() + { + IsSuccess(); + CheckAddedCode(NetworkServerGetActive, "WeaverClientServerAttributeTests.MonoBehaviourServer.MonoBehaviourServer", "ServerOnlyMethod"); + } + + // Checks that first Instructions in MethodBody is addedString + static void CheckAddedCode(string addedString, string className, string methodName) + { + string assemblyName = Path.Combine(WeaverAssembler.OutputDirectory, WeaverAssembler.OutputFile); + using (AssemblyDefinition assembly = AssemblyDefinition.ReadAssembly(assemblyName)) + { + TypeDefinition type = assembly.MainModule.GetType(className); + MethodDefinition method = type.Methods.First(m => m.Name == methodName); + MethodBody body = method.Body; + + Instruction top = body.Instructions[0]; + + Assert.AreEqual(top.OpCode, OpCodes.Call); + Assert.AreEqual(top.Operand.ToString(), addedString); + } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs.meta new file mode 100644 index 000000000..7b9685839 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 54da10f79c18fad49818f5ebc546aa90 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnAbstractMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnAbstractMethod.cs new file mode 100644 index 000000000..8fd30cac0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnAbstractMethod.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.ClientAttributeOnAbstractMethod +{ + abstract class ClientAttributeOnAbstractMethod : NetworkBehaviour + { + [Client] + protected abstract void ClientOnlyMethod(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnOverrideMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnOverrideMethod.cs new file mode 100644 index 000000000..619402086 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnOverrideMethod.cs @@ -0,0 +1,21 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.ClientAttributeOnOverrideMethod +{ + class ClientAttributeOnOverrideMethod : BaseClass + { + [Client] + protected override void ClientOnlyMethod() + { + // test method + } + } + + class BaseClass : NetworkBehaviour + { + protected virtual void ClientOnlyMethod() + { + // test method + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnVirutalMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnVirutalMethod.cs new file mode 100644 index 000000000..a541fc33e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ClientAttributeOnVirutalMethod.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.ClientAttributeOnVirutalMethod +{ + class ClientAttributeOnVirutalMethod : NetworkBehaviour + { + [Client] + protected virtual void ClientOnlyMethod() + { + // test method + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourClient.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourClient.cs new file mode 100644 index 000000000..9f9d381f9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourClient.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverClientServerAttributeTests.MonoBehaviourClient +{ + class MonoBehaviourClient : MonoBehaviour + { + [Client] + void ClientOnlyMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourServer.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourServer.cs new file mode 100644 index 000000000..c5c77bd2e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/MonoBehaviourServer.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverClientServerAttributeTests.MonoBehaviourServer +{ + class MonoBehaviourServer : MonoBehaviour + { + [Server] + void ServerOnlyMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourClient.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourClient.cs new file mode 100644 index 000000000..3fc0ad730 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourClient.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.NetworkBehaviourClient +{ + class NetworkBehaviourClient : NetworkBehaviour + { + [Client] + void ClientOnlyMethod() + { + // test method + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourServer.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourServer.cs new file mode 100644 index 000000000..c5b972f08 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/NetworkBehaviourServer.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.NetworkBehaviourServer +{ + class NetworkBehaviourServer : NetworkBehaviour + { + [Server] + void ServerOnlyMethod() + { + // test method + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassClient.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassClient.cs new file mode 100644 index 000000000..0b3e6ebaf --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassClient.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.RegularClassClient +{ + class RegularClassClient + { + [Client] + void ClientOnlyMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassServer.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassServer.cs new file mode 100644 index 000000000..066bd968a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/RegularClassServer.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.RegularClassServer +{ + class RegularClassServer + { + [Server] + void ServerOnlyMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnAbstractMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnAbstractMethod.cs new file mode 100644 index 000000000..26e83bbc8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnAbstractMethod.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.ServerAttributeOnAbstractMethod +{ + abstract class ServerAttributeOnAbstractMethod : NetworkBehaviour + { + [Server] + protected abstract void ServerOnlyMethod(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnOverrideMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnOverrideMethod.cs new file mode 100644 index 000000000..82beea580 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnOverrideMethod.cs @@ -0,0 +1,21 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.ServerAttributeOnOverrideMethod +{ + class ServerAttributeOnOverrideMethod : BaseClass + { + [Server] + protected override void ServerOnlyMethod() + { + // test method + } + } + + class BaseClass : NetworkBehaviour + { + protected virtual void ServerOnlyMethod() + { + // test method + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnVirutalMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnVirutalMethod.cs new file mode 100644 index 000000000..9e1cccee1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/ServerAttributeOnVirutalMethod.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.ServerAttributeOnVirutalMethod +{ + class ServerAttributeOnVirutalMethod : NetworkBehaviour + { + [Server] + protected virtual void ServerOnlyMethod() + { + // test method + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassClient.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassClient.cs new file mode 100644 index 000000000..563bef3c1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassClient.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.StaticClassClient +{ + static class StaticClassClient + { + [Client] + static void ClientOnlyMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassServer.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassServer.cs new file mode 100644 index 000000000..483eb239b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverClientServerAttributeTests~/StaticClassServer.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverClientServerAttributeTests.StaticClassServer +{ + static class StaticClassServer + { + [Server] + static void ServerOnlyMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs new file mode 100644 index 000000000..158f054ab --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverCommandTests : WeaverTestsBuildFromTestName + { + [Test] + public void CommandCantBeStatic() + { + HasError("CmdCantBeStatic must not be static", + "System.Void WeaverCommandTests.CommandCantBeStatic.CommandCantBeStatic::CmdCantBeStatic()"); + } + + [Test] + public void ErrorForOptionalNetworkConnectionThatIsNotSenderConnection() + { + HasError("CmdFunction has invalid parameter connection, Cannot pass NetworkConnections. Instead use 'NetworkConnectionToClient conn = null' to get the sender's connection on the server", + "System.Void WeaverCommandTests.ErrorForOptionalNetworkConnectionThatIsNotSenderConnection.ErrorForOptionalNetworkConnectionThatIsNotSenderConnection::CmdFunction(Mirror.NetworkConnection)"); + } + + [Test] + public void ErrorForNetworkConnectionThatIsNotSenderConnection() + { + HasError("CmdFunction has invalid parameter connection, Cannot pass NetworkConnections. Instead use 'NetworkConnectionToClient conn = null' to get the sender's connection on the server", + "System.Void WeaverCommandTests.ErrorForNetworkConnectionThatIsNotSenderConnection.ErrorForNetworkConnectionThatIsNotSenderConnection::CmdFunction(Mirror.NetworkConnection)"); + } + + [Test] + public void AbstractCommand() + { + HasError("Abstract Commands are currently not supported, use virtual method instead", + "System.Void WeaverCommandTests.AbstractCommand.AbstractCommand::CmdDoSomething()"); + } + + [Test] + public void OverrideAbstractCommand() + { + HasError("Abstract Commands are currently not supported, use virtual method instead", + "System.Void WeaverCommandTests.OverrideAbstractCommand.BaseBehaviour::CmdDoSomething()"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs.meta new file mode 100644 index 000000000..4760a2ea7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5d2e2e1e726925e4fab5f2460542844d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess.meta new file mode 100644 index 000000000..90cf3e18a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: fac079ccee24451589944f06cfb495ab +timeCreated: 1643081493 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs new file mode 100644 index 000000000..d43697215 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverCommandTests.CommandThatIgnoresAuthority +{ + class CommandThatIgnoresAuthority : NetworkBehaviour + { + [Command(requiresAuthority = false)] + void CmdFunction() + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs.meta new file mode 100644 index 000000000..879f4da18 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthority.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e4265cfab73ff4eef80a01723f6fb5fa +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs new file mode 100644 index 000000000..1b2161ac6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverCommandTests.CommandThatIgnoresAuthorityWithSenderConnection +{ + class CommandThatIgnoresAuthorityWithSenderConnection : NetworkBehaviour + { + [Command(requiresAuthority = false)] + void CmdFunction(NetworkConnectionToClient connection = null) + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs.meta new file mode 100644 index 000000000..59db1afee --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandThatIgnoresAuthorityWithSenderConnection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 10537f40690ce495486d55802e093ada +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs new file mode 100644 index 000000000..5055cbf0f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverCommandTests.CommandValid +{ + class CommandValid : NetworkBehaviour + { + [Command] + void CmdThatIsTotallyValid() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs.meta new file mode 100644 index 000000000..61820d3f1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 3d5568e41a2584f84a1bff2d39dd38a1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs new file mode 100644 index 000000000..416775729 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverCommandTests.CommandWithArguments +{ + class CommandWithArguments : NetworkBehaviour + { + [Command] + void CmdThatIsTotallyValid(int someNumber, NetworkIdentity someTarget) + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs.meta new file mode 100644 index 000000000..1fa1dc61e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithArguments.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1d47ddf01d3e142c1bfa1dd16d96bc27 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs new file mode 100644 index 000000000..7d4963fcb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverCommandTests.CommandWithSenderConnectionAndOtherArgs +{ + class CommandWithSenderConnectionAndOtherArgs : NetworkBehaviour + { + [Command(requiresAuthority = false)] + void CmdFunction(int someNumber, NetworkIdentity someTarget, NetworkConnectionToClient connection = null) + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs.meta new file mode 100644 index 000000000..55cb4e793 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/CommandWithSenderConnectionAndOtherArgs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a5b439a62107449039062d996d83d063 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs new file mode 100644 index 000000000..0784e1700 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs @@ -0,0 +1,23 @@ +using Mirror; + +namespace WeaverCommandTests.OverrideVirtualCallBaseCommand +{ + class OverrideVirtualCallBaseCommand : baseBehaviour + { + [Command] + protected override void CmdDoSomething() + { + // do somethin + base.CmdDoSomething(); + } + } + + class baseBehaviour : NetworkBehaviour + { + [Command] + protected virtual void CmdDoSomething() + { + // do more stuff + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs.meta new file mode 100644 index 000000000..6ce08108b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallBaseCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2a333e7e4ee5544dc8ceb57c1615c2e6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs new file mode 100644 index 000000000..842fa5561 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs @@ -0,0 +1,27 @@ +using Mirror; + +namespace WeaverCommandTests.OverrideVirtualCallsBaseCommandWithMultipleBaseClasses +{ + class OverrideVirtualCallsBaseCommandWithMultipleBaseClasses : middleBehaviour + { + [Command] + protected override void CmdDoSomething() + { + // do somethin + base.CmdDoSomething(); + } + } + + class middleBehaviour : baseBehaviour + { + } + + class baseBehaviour : NetworkBehaviour + { + [Command] + protected virtual void CmdDoSomething() + { + // do more stuff + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs.meta new file mode 100644 index 000000000..61f26600e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithMultipleBaseClasses.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 99460dd9fbe0e4ff2902469dc389ae96 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs new file mode 100644 index 000000000..35736d783 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs @@ -0,0 +1,34 @@ +using Mirror; + +namespace WeaverCommandTests.OverrideVirtualCallsBaseCommandWithOverride +{ + class OverrideVirtualCallsBaseCommandWithOverride : middleBehaviour + { + [Command] + protected override void CmdDoSomething() + { + // do something + base.CmdDoSomething(); + } + } + + + class middleBehaviour : baseBehaviour + { + [Command] + protected override void CmdDoSomething() + { + // do something else + base.CmdDoSomething(); + } + } + + class baseBehaviour : NetworkBehaviour + { + [Command] + protected virtual void CmdDoSomething() + { + // do more stuff + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs.meta new file mode 100644 index 000000000..6f2cd2bc3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCallsBaseCommandWithOverride.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e0058cedd9d5b41cda88b402d7c303be +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs new file mode 100644 index 000000000..c02684b24 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs @@ -0,0 +1,22 @@ +using Mirror; + +namespace WeaverCommandTests.OverrideVirtualCommand +{ + class OverrideVirtualCommand : baseBehaviour + { + [Command] + protected override void CmdDoSomething() + { + // do something + } + } + + class baseBehaviour : NetworkBehaviour + { + [Command] + protected virtual void CmdDoSomething() + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs.meta new file mode 100644 index 000000000..a9676d391 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/OverrideVirtualCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c4f0277f89a9642f794a6bd3300ab09d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs new file mode 100644 index 000000000..493f23cc7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs @@ -0,0 +1,14 @@ +using Mirror; + + +namespace WeaverCommandTests.VirtualCommand +{ + class VirtualCommand : NetworkBehaviour + { + [Command] + protected virtual void CmdDoSomething() + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs.meta new file mode 100644 index 000000000..04aa6930f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests_IsSuccess/VirtualCommand.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4d6761de4ffa14f0797f83e428c9f321 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/AbstractCommand.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/AbstractCommand.cs new file mode 100644 index 000000000..ba1c2b5c9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/AbstractCommand.cs @@ -0,0 +1,11 @@ +using Mirror; + + +namespace WeaverCommandTests.AbstractCommand +{ + abstract class AbstractCommand : NetworkBehaviour + { + [Command] + protected abstract void CmdDoSomething(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/CommandCantBeStatic.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/CommandCantBeStatic.cs new file mode 100644 index 000000000..e1ae6658f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/CommandCantBeStatic.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverCommandTests.CommandCantBeStatic +{ + class CommandCantBeStatic : NetworkBehaviour + { + [Command] + static void CmdCantBeStatic() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForNetworkConnectionThatIsNotSenderConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForNetworkConnectionThatIsNotSenderConnection.cs new file mode 100644 index 000000000..1443a861b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForNetworkConnectionThatIsNotSenderConnection.cs @@ -0,0 +1,14 @@ +using Mirror; + + +namespace WeaverCommandTests.ErrorForNetworkConnectionThatIsNotSenderConnection +{ + class ErrorForNetworkConnectionThatIsNotSenderConnection : NetworkBehaviour + { + [Command(requiresAuthority = false)] + void CmdFunction(NetworkConnection connection) + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForOptionalNetworkConnectionThatIsNotSenderConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForOptionalNetworkConnectionThatIsNotSenderConnection.cs new file mode 100644 index 000000000..b50c7f31d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/ErrorForOptionalNetworkConnectionThatIsNotSenderConnection.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverCommandTests.ErrorForOptionalNetworkConnectionThatIsNotSenderConnection +{ + class ErrorForOptionalNetworkConnectionThatIsNotSenderConnection : NetworkBehaviour + { + [Command(requiresAuthority = false)] + void CmdFunction(NetworkConnection connection = null) + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/OverrideAbstractCommand.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/OverrideAbstractCommand.cs new file mode 100644 index 000000000..ae6679d3a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverCommandTests~/OverrideAbstractCommand.cs @@ -0,0 +1,20 @@ +using Mirror; + + +namespace WeaverCommandTests.OverrideAbstractCommand +{ + class OverrideAbstractCommand : BaseBehaviour + { + [Command] + protected override void CmdDoSomething() + { + // do something + } + } + + abstract class BaseBehaviour : NetworkBehaviour + { + [Command] + protected abstract void CmdDoSomething(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess.meta new file mode 100644 index 000000000..b00b4ea1d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 70d3b9bd4b7242e3a3c4170a476e7f51 +timeCreated: 1643081656 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs new file mode 100644 index 000000000..d5b55b723 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs @@ -0,0 +1,22 @@ +using Mirror; + +namespace WeaverGeneralTests.RecursionCount +{ + class RecursionCount : NetworkBehaviour + { + public class Potato0 + { + public int hamburgers = 17; + public Potato1 p1; + } + + public class Potato1 + { + public int hamburgers = 18; + public Potato0 p0; + } + + [SyncVar] + Potato0 recursionTime; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs.meta new file mode 100644 index 000000000..50fc4ae12 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/RecursionCount.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: df60a572fc56841c284952761332bcec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs new file mode 100644 index 000000000..654d64a0f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs @@ -0,0 +1,40 @@ +using Mirror; +using UnityEngine; + +namespace WeaverGeneralTests.TestingScriptableObjectArraySerialization +{ + public static class CustomSerializer + { + public static void Writedata(this NetworkWriter writer, Data arg) + { + writer.WriteInt(arg.Var1); + } + + public static Data Readdata(this NetworkReader reader) + { + return new Data + { + Var1 = reader.ReadInt() + }; + } + } + + public class Data : ScriptableObject + { + public int Var1; + } + + public class TestingScriptableObjectArraySerialization : NetworkBehaviour + { + [Command] + public void + // This gonna give error saying-- Mirror.Weaver error: + // Cannot generate writer for scriptable object Data[]. Use a supported type or provide a custom writer + CmdwriteArraydata( + Data[] arg) + { + + //some code + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs.meta new file mode 100644 index 000000000..bbe699aa7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneralTests_IsSuccess/TestingScriptableObjectArraySerialization.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2926d9420f39349e893ddec0a517529b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess.meta new file mode 100644 index 000000000..78436b223 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 7588efe7b5b844dab82a9f3fb4360e7a +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs new file mode 100644 index 000000000..0673eb49d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs @@ -0,0 +1,14 @@ +using Mirror; +using Mirror.Weaver.Tests.Extra; + +namespace GeneratedReaderWriter.CanUseCustomReadWriteForTypesFromDifferentAssemblies +{ + public class CanUseCustomReadWriteForTypesFromDifferentAssemblies : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeDataWithWriter data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs.meta new file mode 100644 index 000000000..d70d24422 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CanUseCustomReadWriteForTypesFromDifferentAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b99201f4b462349fbbbaa30d63b7383c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs new file mode 100644 index 000000000..78fc9b20b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs @@ -0,0 +1,14 @@ +using Mirror; +using Mirror.Weaver.Tests.Extra; + +namespace GeneratedReaderWriter.CreatesForClassFromDifferentAssemblies +{ + public class CreatesForClassFromDifferentAssemblies : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeDataClass data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs.meta new file mode 100644 index 000000000..69ba0e983 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e098c02c1a4d543fea35eacb265bf185 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs new file mode 100644 index 000000000..ddb3a95a4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs @@ -0,0 +1,14 @@ +using Mirror; +using Mirror.Weaver.Tests.Extra; + +namespace GeneratedReaderWriter.CreatesForClassFromDifferentAssembliesWithValidConstructor +{ + public class CreatesForClassFromDifferentAssembliesWithValidConstructor : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeDataClassWithConstructor data) + { + // empty + } + } +} \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs.meta new file mode 100644 index 000000000..a382f1870 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForClassFromDifferentAssembliesWithValidConstructor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 66afc9de1e55649088cc2e0ddc87e2bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs new file mode 100644 index 000000000..e9135dee6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs @@ -0,0 +1,14 @@ +using Mirror; +using Mirror.Weaver.Tests.Extra; + +namespace GeneratedReaderWriter.CreatesForComplexTypeFromDifferentAssemblies +{ + public class CreatesForComplexTypeFromDifferentAssemblies : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(ComplexData data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs.meta new file mode 100644 index 000000000..1cc3ce301 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForComplexTypeFromDifferentAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0cca8bd4f61264c69b5cebb0d06e6ccd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs new file mode 100644 index 000000000..6ce7e3c35 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs @@ -0,0 +1,14 @@ +using Mirror; +using Mirror.Weaver.Tests.Extra; + +namespace GeneratedReaderWriter.CreatesForStructFromDifferentAssemblies +{ + public class CreatesForStructFromDifferentAssemblies : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeData data) + { + // empty + } + } +} \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs.meta new file mode 100644 index 000000000..fe30ea775 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForStructFromDifferentAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4fe20050b18ad437eaea18c81e4b7be5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs new file mode 100644 index 000000000..f36998481 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs @@ -0,0 +1,19 @@ +using Mirror; +using Mirror.Weaver.Tests.Extra; + +namespace GeneratedReaderWriter.CreatesForTypeThatUsesDifferentAssemblies +{ + public class CreatesForTypeThatUsesDifferentAssemblies : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(DataHolder data) + { + // empty + } + } + public struct DataHolder + { + public AnotherData another; + public float q; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs.meta new file mode 100644 index 000000000..a2c4f8f01 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterAnotherAssemblyTests_IsSuccess/CreatesForTypeThatUsesDifferentAssemblies.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d2ac5c5eaeb6241ccb87cd4d7b104f5b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs new file mode 100644 index 000000000..6cd4e232e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs @@ -0,0 +1,133 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverGeneratedReaderWriterTests : WeaverTestsBuildFromTestName + { + [Test] + public void GivesErrorForClassWithNoValidConstructor() + { + HasError("SomeOtherData can't be deserialized because it has no default constructor. Don't use SomeOtherData in [SyncVar]s, Rpcs, Cmds, etc.", + "GeneratedReaderWriter.GivesErrorForClassWithNoValidConstructor.SomeOtherData"); + } + + [Test] + public void GivesErrorWhenUsingUnityAsset() + { + HasError("Material can't be deserialized because it has no default constructor. Don't use Material in [SyncVar]s, Rpcs, Cmds, etc.", + "UnityEngine.Material"); + } + + [Test] + public void GivesErrorWhenUsingObject() + { + // TODO: decide if we want to block sending of Object + // would only want to be send as an arg as a base type for an Inherited object + HasError("Cannot generate writer for Object. Use a supported type or provide a custom writer", + "UnityEngine.Object"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for Object. Use a supported type or provide a custom reader", + // "UnityEngine.Object"); + } + + [Test] + public void GivesErrorWhenUsingScriptableObject() + { + // TODO: decide if we want to block sending of ScripableObject + // would only want to be send as an arg as a base type for an Inherited object + HasError("Cannot generate writer for ScriptableObject. Use a supported type or provide a custom writer", + "UnityEngine.ScriptableObject"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for ScriptableObject. Use a supported type or provide a custom reader", + // "UnityEngine.ScriptableObject"); + } + + [Test] + public void GivesErrorWhenUsingMonoBehaviour() + { + HasError("Cannot generate writer for component type MonoBehaviour. Use a supported type or provide a custom writer", + "UnityEngine.MonoBehaviour"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for component type MonoBehaviour. Use a supported type or provide a custom reader", + // "UnityEngine.MonoBehaviour"); + } + + [Test] + public void GivesErrorWhenUsingTypeInheritedFromMonoBehaviour() + { + HasError("Cannot generate writer for component type MyBehaviour. Use a supported type or provide a custom writer", + "GeneratedReaderWriter.GivesErrorWhenUsingTypeInheritedFromMonoBehaviour.MyBehaviour"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for component type MyBehaviour. Use a supported type or provide a custom reader", + // "GeneratedReaderWriter.GivesErrorWhenUsingTypeInheritedFromMonoBehaviour.MyBehaviour"); + } + + [Test] + public void GivesErrorWhenUsingInterface() + { + HasError("Cannot generate writer for interface IData. Use a supported type or provide a custom writer", + "GeneratedReaderWriter.GivesErrorWhenUsingInterface.IData"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for interface IData. Use a supported type or provide a custom reader", + // "GeneratedReaderWriter.GivesErrorWhenUsingInterface.IData"); + } + + [Test] + public void GivesErrorWhenUsingAbstractClass() + { + HasError("Cannot generate writer for abstract class DataBase. Use a supported type or provide a custom writer", + "GeneratedReaderWriter.GivesErrorWhenUsingAbstractClass.DataBase"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for abstract class DataBase. Use a supported type or provide a custom reader", + // "GeneratedReaderWriter.GivesErrorWhenUsingAbstractClass.DataBase"); + } + + [Test] + public void GivesErrorForMultidimensionalArray() + { + HasError("Int32[0...,0...] is an unsupported type. Multidimensional arrays are not supported", + "System.Int32[0...,0...]"); + } + + [Test] + public void GivesErrorForInvalidArrayType() + { + HasError("Cannot generate writer for UnityEngine.MonoBehaviour[]. Use a supported type or provide a custom writer", + "UnityEngine.MonoBehaviour[]"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for Array because element MonoBehaviour does not have a reader. Use a supported type or provide a custom reader", + // "UnityEngine.MonoBehaviour[]"); + } + + [Test] + public void GivesErrorForInvalidArraySegmentType() + { + HasError("Cannot generate writer for System.ArraySegment`1. Use a supported type or provide a custom writer", + "System.ArraySegment`1"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for ArraySegment because element MonoBehaviour does not have a reader. Use a supported type or provide a custom reader", + // "System.ArraySegment`1"); + } + + [Test] + public void GivesErrorForInvalidListType() + { + HasError("Cannot generate writer for System.Collections.Generic.List`1. Use a supported type or provide a custom writer", + "System.Collections.Generic.List`1"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for List because element MonoBehaviour does not have a reader. Use a supported type or provide a custom reader", + // "System.Collections.Generic.List`1"); + } + + [Test, Ignore("Enable again when we don't have obsoletes in NetworkWriter anymore.")] + public void GivesWarningWhenRegisteringExistingExtensionMethod() + { + const string typeName = "GeneratedReaderWriter.GivesWarningWhenRegisteringExistingExtensionMethod.MyType"; + HasNoErrors(); + HasWarning($"Registering a Write method for {typeName} when one already exists", + "System.Void GeneratedReaderWriter.GivesWarningWhenRegisteringExistingExtensionMethod.ReadWriteExtension::WriteMyType2(Mirror.NetworkWriter,GeneratedReaderWriter.GivesWarningWhenRegisteringExistingExtensionMethod.MyType)"); + HasWarning($"Registering a Read method for {typeName} when one already exists", + "GeneratedReaderWriter.GivesWarningWhenRegisteringExistingExtensionMethod.MyType GeneratedReaderWriter.GivesWarningWhenRegisteringExistingExtensionMethod.ReadWriteExtension::ReadMyType2(Mirror.NetworkReader)"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs.meta new file mode 100644 index 000000000..53522fbf4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0ee83eef537268344880fa60f2c7a059 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess.meta new file mode 100644 index 000000000..321d91334 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 481ded00d806459b98c56f88b4c63cac +timeCreated: 1643082170 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs new file mode 100644 index 000000000..831ab34ae --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs @@ -0,0 +1,62 @@ +using Mirror; + +namespace GeneratedReaderWriter.CanUseCustomReadWriteForAbstractClass +{ + public class CanUseCustomReadWriteForAbstractClass : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(DataBase data) + { + // empty + } + } + + public abstract class DataBase + { + public int someField; + public abstract int id { get; } + } + + public class SomeData : DataBase + { + public float anotherField; + public override int id => 1; + } + + public static class DataReadWrite + { + public static void WriteData(this NetworkWriter writer, DataBase data) + { + writer.WriteInt(data.id); + // write extra stuff depending on id here + writer.WriteInt(data.someField); + + if (data.id == 1) + { + SomeData someData = (SomeData)data; + writer.WriteFloat(someData.anotherField); + } + } + + public static DataBase ReadData(this NetworkReader reader) + { + int id = reader.ReadInt(); + + int someField = reader.ReadInt(); + DataBase data = null; + if (data.id == 1) + { + SomeData someData = new SomeData() + { + someField = someField + }; + // read extra stuff depending on id here + + someData.anotherField = reader.ReadFloat(); + + data = someData; + } + return data; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs.meta new file mode 100644 index 000000000..38342051d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForAbstractClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ec3aff614cf064fcfb647c5bf2427ab9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs new file mode 100644 index 000000000..e80f22613 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs @@ -0,0 +1,44 @@ +using Mirror; + +namespace GeneratedReaderWriter.CanUseCustomReadWriteForInterfaces +{ + public class CanUseCustomReadWriteForInterfaces : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(IData data) + { + // empty + } + } + + public interface IData + { + int id { get; } + } + + public class SomeData : IData + { + public int id => 1; + } + + public static class DataReadWrite + { + public static void WriteData(this NetworkWriter writer, IData data) + { + writer.WriteInt(data.id); + // write extra stuff depending on id here + } + + public static IData ReadData(this NetworkReader reader) + { + int id = reader.ReadInt(); + // do something with id + + SomeData someData = new SomeData(); + // read extra stuff depending on id here + + return someData; + } + } + +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs.meta new file mode 100644 index 000000000..86c8e62de --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CanUseCustomReadWriteForInterfaces.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d362bab10072f4254b5b5ec2dec6a219 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs new file mode 100644 index 000000000..d43da5bf7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs @@ -0,0 +1,14 @@ +using System; +using Mirror; + +namespace GeneratedReaderWriter.CreatesForArraySegment +{ + public class CreatesForArraySegment : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(ArraySegment data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs.meta new file mode 100644 index 000000000..8189ae88d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForArraySegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b8e170776a26948e5a371acd9d108e23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs new file mode 100644 index 000000000..8441d15ca --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs @@ -0,0 +1,18 @@ +using Mirror; + +namespace GeneratedReaderWriter.CreatesForClass +{ + public class CreatesForClass : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeOtherData data) + { + // empty + } + } + + public class SomeOtherData + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs.meta new file mode 100644 index 000000000..5a1dae021 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c40f8d67a65164c4cbee03c0e0e25238 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs new file mode 100644 index 000000000..81ad2cec1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs @@ -0,0 +1,23 @@ +using Mirror; + + +namespace GeneratedReaderWriter.CreatesForClassInherited +{ + public class CreatesForClassInherited : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeOtherData data) + { + // empty + } + } + + public class BaseData + { + public bool yes; + } + public class SomeOtherData : BaseData + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs.meta new file mode 100644 index 000000000..63021aa18 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassInherited.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7403ae2e88bb94bb68fb52724eb80b9e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs new file mode 100644 index 000000000..8c8e604c9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs @@ -0,0 +1,23 @@ +using Mirror; + +namespace GeneratedReaderWriter.CreatesForClassWithValidConstructor +{ + public class CreatesForClassWithValidConstructor : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeOtherData data) + { + // empty + } + } + + public class SomeOtherData + { + public int usefulNumber; + + public SomeOtherData() + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs.meta new file mode 100644 index 000000000..1f272c650 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForClassWithValidConstructor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c8bdbd0be145445769dd26795ebb1e14 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs new file mode 100644 index 000000000..379972c02 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs @@ -0,0 +1,21 @@ +using Mirror; + +namespace GeneratedReaderWriter.CreatesForEnums +{ + class CreatesForEnums : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(FunEnum data) + { + // empty + } + } + + public enum FunEnum + { + A, + B, + C, + D, + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs.meta new file mode 100644 index 000000000..65782e104 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForEnums.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e9b99d242e2d4ec791120b90992f9f9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs new file mode 100644 index 000000000..bc28938ac --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs @@ -0,0 +1,19 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.CreatesForInheritedFromScriptableObject +{ + public class CreatesForInheritedFromScriptableObject : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(DataScriptableObject data) + { + // empty + } + } + + public class DataScriptableObject : ScriptableObject + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs.meta new file mode 100644 index 000000000..b3ac125e8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForInheritedFromScriptableObject.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: eaa774dd59f314483be65bb1fecaf240 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs new file mode 100644 index 000000000..12dabd46b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs @@ -0,0 +1,14 @@ +using System.Collections.Generic; +using Mirror; + +namespace GeneratedReaderWriter.CreatesForList +{ + public class CreatesForList : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(List data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs.meta new file mode 100644 index 000000000..210bcc75f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 01e98a567df124dbdae4da004b786676 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs new file mode 100644 index 000000000..06e535949 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs @@ -0,0 +1,21 @@ +using System; +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.CreatesForStructArraySegment +{ + public class CreatesForStructArraySegment : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(ArraySegment data) + { + // empty + } + } + + public struct MyStruct + { + public int someValue; + public Vector3 anotherValue; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs.meta new file mode 100644 index 000000000..41b8c8ed8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructArraySegment.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4115a88d5bbc6468193943b69c777be5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs new file mode 100644 index 000000000..7b21dbd04 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs @@ -0,0 +1,22 @@ +using System.Collections.Generic; +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.CreatesForStructList +{ + public class CreatesForStructList : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(List data) + { + // empty + } + } + + public struct MyStruct + { + public int someValue; + public Vector3 anotherValue; + } +} + diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs.meta new file mode 100644 index 000000000..cbbd21de2 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5794897895bef48588d561811d35fe3a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs new file mode 100644 index 000000000..67ccb3e02 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs @@ -0,0 +1,18 @@ +using Mirror; + +namespace GeneratedReaderWriter.CreatesForStructs +{ + public class CreatesForStructs : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeOtherData data) + { + // empty + } + } + + public struct SomeOtherData + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs.meta new file mode 100644 index 000000000..a22041537 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/CreatesForStructs.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 53df702fb349b4529bdc8d0ae2795cbc +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs new file mode 100644 index 000000000..89509f109 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs @@ -0,0 +1,20 @@ +using Mirror; + +namespace GeneratedReaderWriter.ExcludesNonSerializedFields +{ + public class ExcludesNonSerializedFields : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeDataUsingNonSerialized data) + { + // empty + } + } + + public struct SomeDataUsingNonSerialized + { + public int usefulNumber; + // Object is a not allowed type + [System.NonSerialized] public UnityEngine.Object obj; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs.meta new file mode 100644 index 000000000..aef32872c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/ExcludesNonSerializedFields.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a30a5f4499dde487baad74ec2eb09279 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs new file mode 100644 index 000000000..236c54e4e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace GeneratedReaderWriter.GivesErrorForJaggedArray +{ + public class GivesErrorForJaggedArray : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(int[][] data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs.meta new file mode 100644 index 000000000..ec4f4c051 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests_IsSuccess/GivesErrorForJaggedArray.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: bfb5cd2f56ad544369df0ec231dd91e8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForClassWithNoValidConstructor.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForClassWithNoValidConstructor.cs new file mode 100644 index 000000000..440bdf53a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForClassWithNoValidConstructor.cs @@ -0,0 +1,23 @@ +using Mirror; + +namespace GeneratedReaderWriter.GivesErrorForClassWithNoValidConstructor +{ + public class GivesErrorForClassWithNoValidConstructor : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(SomeOtherData data) + { + // empty + } + } + + public class SomeOtherData + { + public int usefulNumber; + + public SomeOtherData(int usefulNumber) + { + this.usefulNumber = usefulNumber; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArraySegmentType.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArraySegmentType.cs new file mode 100644 index 000000000..d81c3ddbc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArraySegmentType.cs @@ -0,0 +1,15 @@ +using System; +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorForInvalidArraySegmentType +{ + public class GivesErrorForInvalidArraySegmentType : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(ArraySegment data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArrayType.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArrayType.cs new file mode 100644 index 000000000..a63124e9d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidArrayType.cs @@ -0,0 +1,14 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorForInvalidArrayType +{ + public class GivesErrorForInvalidArrayType : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(MonoBehaviour[] data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidListType.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidListType.cs new file mode 100644 index 000000000..8dfaeab11 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForInvalidListType.cs @@ -0,0 +1,15 @@ +using System.Collections.Generic; +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorForInvalidListType +{ + public class GivesErrorForInvalidListType : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(List data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForMultidimensionalArray.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForMultidimensionalArray.cs new file mode 100644 index 000000000..297fafa3f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorForMultidimensionalArray.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace GeneratedReaderWriter.GivesErrorForMultidimensionalArray +{ + public class GivesErrorForMultidimensionalArray : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(int[,] data) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingAbstractClass.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingAbstractClass.cs new file mode 100644 index 000000000..a8929f446 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingAbstractClass.cs @@ -0,0 +1,19 @@ +using Mirror; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingAbstractClass +{ + public class GivesErrorWhenUsingAbstractClass : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(DataBase data) + { + // empty + } + } + + public abstract class DataBase + { + public int someField; + public abstract int id { get; } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingInterface.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingInterface.cs new file mode 100644 index 000000000..2feaab8da --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingInterface.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingInterface +{ + public class GivesErrorWhenUsingInterface : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(IData data) + { + // empty + } + } + + public interface IData { } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingMonoBehaviour.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingMonoBehaviour.cs new file mode 100644 index 000000000..28ff7dcec --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingMonoBehaviour.cs @@ -0,0 +1,14 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingMonoBehaviour +{ + public class GivesErrorWhenUsingMonoBehaviour : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(MonoBehaviour behaviour) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingObject.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingObject.cs new file mode 100644 index 000000000..753b8a80b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingObject.cs @@ -0,0 +1,14 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingObject +{ + public class GivesErrorWhenUsingObject : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(Object obj) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingScriptableObject.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingScriptableObject.cs new file mode 100644 index 000000000..fe0c3a55d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingScriptableObject.cs @@ -0,0 +1,14 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingScriptableObject +{ + public class GivesErrorWhenUsingScriptableObject : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(ScriptableObject obj) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingTypeInheritedFromMonoBehaviour.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingTypeInheritedFromMonoBehaviour.cs new file mode 100644 index 000000000..628070d62 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingTypeInheritedFromMonoBehaviour.cs @@ -0,0 +1,19 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingTypeInheritedFromMonoBehaviour +{ + public class GivesErrorWhenUsingTypeInheritedFromMonoBehaviour : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(MyBehaviour behaviour) + { + // empty + } + } + + public class MyBehaviour : MonoBehaviour + { + public int usefulNumber; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingUnityAsset.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingUnityAsset.cs new file mode 100644 index 000000000..db2fbd8dd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesErrorWhenUsingUnityAsset.cs @@ -0,0 +1,14 @@ +using Mirror; +using UnityEngine; + +namespace GeneratedReaderWriter.GivesErrorWhenUsingUnityAsset +{ + public class GivesErrorForClassWithNoValidConstructor : NetworkBehaviour + { + [ClientRpc] + public void RpcDoSomething(Material material) + { + // empty + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesWarningWhenRegisteringExistingExtensionMethod.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesWarningWhenRegisteringExistingExtensionMethod.cs new file mode 100644 index 000000000..733589b79 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverGeneratedReaderWriterTests~/GivesWarningWhenRegisteringExistingExtensionMethod.cs @@ -0,0 +1,32 @@ +using Mirror; + +namespace GeneratedReaderWriter.GivesWarningWhenRegisteringExistingExtensionMethod +{ + public struct MyType + { + public int number; + } + + public static class ReadWriteExtension + { + public static void WriteMyType(this NetworkWriter writer, MyType data) + { + writer.WriteInt(data.number); + } + + public static void WriteMyType2(this NetworkWriter writer, MyType data) + { + writer.WriteInt(data.number); + } + + public static MyType ReadMyType(this NetworkReader reader) + { + return new MyType { number = reader.ReadInt() }; + } + + public static MyType ReadMyType2(this NetworkReader reader) + { + return new MyType { number = reader.ReadInt() }; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs new file mode 100644 index 000000000..21db09456 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs @@ -0,0 +1,30 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverMessageTests : WeaverTestsBuildFromTestName + { + [Test] + public void MessageMemberGeneric() + { + HasError("Cannot generate reader for generic variable HasGeneric`1. Use a supported type or provide a custom reader", + "WeaverMessageTests.MessageMemberGeneric.HasGeneric`1"); + HasError("invalidField has an unsupported type", + "WeaverMessageTests.MessageMemberGeneric.HasGeneric`1 WeaverMessageTests.MessageMemberGeneric.MessageMemberGeneric::invalidField"); + HasError("Cannot generate writer for generic type HasGeneric`1. Use a supported type or provide a custom writer", + "WeaverMessageTests.MessageMemberGeneric.HasGeneric`1"); + } + + [Test] + public void MessageMemberInterface() + { + HasError("Cannot generate reader for interface SuperCoolInterface. Use a supported type or provide a custom reader", + "WeaverMessageTests.MessageMemberInterface.SuperCoolInterface"); + HasError("invalidField has an unsupported type", + "WeaverMessageTests.MessageMemberInterface.SuperCoolInterface WeaverMessageTests.MessageMemberInterface.MessageMemberInterface::invalidField"); + HasError("Cannot generate writer for interface SuperCoolInterface. Use a supported type or provide a custom writer", + "WeaverMessageTests.MessageMemberInterface.SuperCoolInterface"); + } + } +} + diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs.meta new file mode 100644 index 000000000..dae37dbf4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 29c6628d42055df42a471ce273c5a7c5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess.meta new file mode 100644 index 000000000..1e58f68c3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 189a9592d5ca4459aadeb4933ad579b4 +timeCreated: 1643082574 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs new file mode 100644 index 000000000..0ca28ecea --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs @@ -0,0 +1,20 @@ +using Mirror; + +namespace WeaverMessageTests.AbstractMessageMethods +{ + abstract class AbstractMessage : NetworkMessage + { + public abstract void Deserialize(NetworkReader reader); + public abstract void Serialize(NetworkWriter writer); + } + + class OverrideMessage : AbstractMessage + { + public int someValue; + + // Mirror will fill out these empty methods + + public override void Serialize(NetworkWriter writer) { } + public override void Deserialize(NetworkReader reader) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs.meta new file mode 100644 index 000000000..2e91fefcb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/AbstractMessageMethods.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e2526f960017544a9ac51109e40e5ca2 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs new file mode 100644 index 000000000..cd8113b94 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverMessageTests.MessageNestedInheritance +{ + public class Message : NetworkMessage + { + public class Request : Message + { + + } + + public class Response : Message + { + public int errorCode; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs.meta new file mode 100644 index 000000000..f8259e0b0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageNestedInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57ca45dd13a334f3e82bec8161e2a309 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs new file mode 100644 index 000000000..9d861fe86 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs @@ -0,0 +1,16 @@ +using System; +using Mirror; +using UnityEngine; + +namespace WeaverMessageTests.MessageSelfReferencing +{ + class MessageSelfReferencing : NetworkMessage + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public MessageSelfReferencing selfReference = new MessageSelfReferencing(); + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs.meta new file mode 100644 index 000000000..64a2e846f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageSelfReferencing.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 80d871bb7d76d4ba4bb61c4463a18611 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs new file mode 100644 index 000000000..a4f130701 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs @@ -0,0 +1,15 @@ +using System; +using Mirror; +using UnityEngine; + +namespace WeaverMessageTests.MessageValid +{ + class MessageValid : NetworkMessage + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs.meta new file mode 100644 index 000000000..eb84a92d6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f9e80c0fd45740d1923469de369a8d3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs new file mode 100644 index 000000000..31bae57e4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs @@ -0,0 +1,20 @@ +using System; +using Mirror; +using UnityEngine; + +namespace WeaverMessageTests.MessageWithBaseClass +{ + class MessageWithBaseClass : SomeBaseMessage + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public byte[] payload; + } + + class SomeBaseMessage : NetworkMessage + { + public int myExtraType; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs.meta new file mode 100644 index 000000000..c457c158f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests_IsSuccess/MessageWithBaseClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7ad4ec02f843347b7bbbc9b85d455124 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberGeneric.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberGeneric.cs new file mode 100644 index 000000000..9dcca9b9b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberGeneric.cs @@ -0,0 +1,18 @@ +using System; +using Mirror; +using UnityEngine; + +namespace WeaverMessageTests.MessageMemberGeneric +{ + class HasGeneric { } + + class MessageMemberGeneric : NetworkMessage + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public HasGeneric invalidField; + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberInterface.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberInterface.cs new file mode 100644 index 000000000..96c55221f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMessageTests~/MessageMemberInterface.cs @@ -0,0 +1,18 @@ +using System; +using Mirror; +using UnityEngine; + +namespace WeaverMessageTests.MessageMemberInterface +{ + interface SuperCoolInterface { } + + class MessageMemberInterface : NetworkMessage + { + public uint netId; + public Guid assetId; + public Vector3 position; + public Quaternion rotation; + public SuperCoolInterface invalidField; + public byte[] payload; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs new file mode 100644 index 000000000..3469e7bd5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs @@ -0,0 +1,42 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverMonoBehaviourTests : WeaverTestsBuildFromTestName + { + [Test] + public void MonoBehaviourSyncVar() + { + HasError("SyncVar potato must be inside a NetworkBehaviour. MonoBehaviourSyncVar is not a NetworkBehaviour", + "System.Int32 WeaverMonoBehaviourTests.MonoBehaviourSyncVar.MonoBehaviourSyncVar::potato"); + } + + [Test] + public void MonoBehaviourSyncList() + { + HasError("potato is a SyncObject and must be inside a NetworkBehaviour. MonoBehaviourSyncList is not a NetworkBehaviour", + "Mirror.SyncList`1 WeaverMonoBehaviourTests.MonoBehaviourSyncList.MonoBehaviourSyncList::potato"); + } + + [Test] + public void MonoBehaviourCommand() + { + HasError("Command CmdThisCantBeOutsideNetworkBehaviour must be declared inside a NetworkBehaviour", + "System.Void WeaverMonoBehaviourTests.MonoBehaviourCommand.MonoBehaviourCommand::CmdThisCantBeOutsideNetworkBehaviour()"); + } + + [Test] + public void MonoBehaviourClientRpc() + { + HasError("ClientRpc RpcThisCantBeOutsideNetworkBehaviour must be declared inside a NetworkBehaviour", + "System.Void WeaverMonoBehaviourTests.MonoBehaviourClientRpc.MonoBehaviourClientRpc::RpcThisCantBeOutsideNetworkBehaviour()"); + } + + [Test] + public void MonoBehaviourTargetRpc() + { + HasError("TargetRpc TargetThisCantBeOutsideNetworkBehaviour must be declared inside a NetworkBehaviour", + "System.Void WeaverMonoBehaviourTests.MonoBehaviourTargetRpc.MonoBehaviourTargetRpc::TargetThisCantBeOutsideNetworkBehaviour(Mirror.NetworkConnection)"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs.meta new file mode 100644 index 000000000..9a127a7d8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 49f43099dd42441488962e7baf02a35e +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess.meta new file mode 100644 index 000000000..fb0609a12 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ec2bed0a50964e698c382457a790ef26 +timeCreated: 1643082698 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs new file mode 100644 index 000000000..a94b5e273 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourClient +{ + class MonoBehaviourClient : MonoBehaviour + { + [Client] + void ThisCantBeOutsideNetworkBehaviour() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs.meta new file mode 100644 index 000000000..3d1d9bcf3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClient.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2c0dbb08bb37f4bc0bdf0c2b0c414adb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs new file mode 100644 index 000000000..49c22323e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourClientCallback +{ + class MonoBehaviourClientCallback : MonoBehaviour + { + [ClientCallback] + void ThisCantBeOutsideNetworkBehaviour() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs.meta new file mode 100644 index 000000000..332609184 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourClientCallback.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b06345372a7df4b31b37cac6fc6e3b55 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs new file mode 100644 index 000000000..548812654 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourServer +{ + class MonoBehaviourServer : MonoBehaviour + { + [Server] + void ThisCantBeOutsideNetworkBehaviour() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs.meta new file mode 100644 index 000000000..11c1c1334 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 06ff2af14a7e04598b10c38cebb0dfd8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs new file mode 100644 index 000000000..1ce6075cb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourServerCallback +{ + class MonoBehaviourServerCallback : MonoBehaviour + { + [ServerCallback] + void ThisCantBeOutsideNetworkBehaviour() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs.meta new file mode 100644 index 000000000..7ab973762 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourServerCallback.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 267c93d64d13540e1a8440b0327bc4a3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs new file mode 100644 index 000000000..5268ac902 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs @@ -0,0 +1,11 @@ +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourValid +{ + class MonoBehaviourValid : MonoBehaviour + { +#pragma warning disable CS0414 + int monkeys = 12; +#pragma warning restore CS0414 + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs.meta new file mode 100644 index 000000000..458a598a3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests_IsSuccess/MonoBehaviourValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 92be36d5dc0cd45a3a73c7185c51e4ff +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourClientRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourClientRpc.cs new file mode 100644 index 000000000..c85b92f12 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourClientRpc.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourClientRpc +{ + class MonoBehaviourClientRpc : MonoBehaviour + { + [ClientRpc] + void RpcThisCantBeOutsideNetworkBehaviour() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourCommand.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourCommand.cs new file mode 100644 index 000000000..fd35c0551 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourCommand.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourCommand +{ + class MonoBehaviourCommand : MonoBehaviour + { + [Command] + void CmdThisCantBeOutsideNetworkBehaviour() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncList.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncList.cs new file mode 100644 index 000000000..cba92f48d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncList.cs @@ -0,0 +1,10 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourSyncList +{ + class MonoBehaviourSyncList : MonoBehaviour + { + SyncList potato; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncVar.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncVar.cs new file mode 100644 index 000000000..5727bab49 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourSyncVar.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourSyncVar +{ + class MonoBehaviourSyncVar : MonoBehaviour + { + [SyncVar] + int potato; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourTargetRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourTargetRpc.cs new file mode 100644 index 000000000..21318e671 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverMonoBehaviourTests~/MonoBehaviourTargetRpc.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverMonoBehaviourTests.MonoBehaviourTargetRpc +{ + class MonoBehaviourTargetRpc : MonoBehaviour + { + [TargetRpc] + void TargetThisCantBeOutsideNetworkBehaviour(NetworkConnection nc) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs new file mode 100644 index 000000000..a794a2d7e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs @@ -0,0 +1,243 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverNetworkBehaviourTests : WeaverTestsBuildFromTestName + { + [Test] + public void NetworkBehaviourGeneric() + { + HasError("NetworkBehaviourGeneric`1 cannot have generic parameters", + "WeaverNetworkBehaviourTests.NetworkBehaviourGeneric.NetworkBehaviourGeneric`1"); + } + + [Test] + public void NetworkBehaviourCmdGenericParam() + { + HasError("CmdCantHaveGeneric cannot have generic parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdGenericParam.NetworkBehaviourCmdGenericParam::CmdCantHaveGeneric()"); + } + + [Test] + public void NetworkBehaviourCmdCoroutine() + { + HasError("CmdCantHaveCoroutine cannot be a coroutine", + "System.Collections.IEnumerator WeaverNetworkBehaviourTests.NetworkBehaviourCmdCoroutine.NetworkBehaviourCmdCoroutine::CmdCantHaveCoroutine()"); + } + + [Test] + public void NetworkBehaviourCmdVoidReturn() + { + HasError("CmdCantHaveNonVoidReturn cannot return a value. Make it void instead", + "System.Int32 WeaverNetworkBehaviourTests.NetworkBehaviourCmdVoidReturn.NetworkBehaviourCmdVoidReturn::CmdCantHaveNonVoidReturn()"); + } + + [Test] + public void NetworkBehaviourTargetRpcGenericParam() + { + HasError("TargetRpcCantHaveGeneric cannot have generic parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcGenericParam.NetworkBehaviourTargetRpcGenericParam::TargetRpcCantHaveGeneric()"); + } + + [Test] + public void NetworkBehaviourTargetRpcCoroutine() + { + HasError("TargetRpcCantHaveCoroutine cannot be a coroutine", + "System.Collections.IEnumerator WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcCoroutine.NetworkBehaviourTargetRpcCoroutine::TargetRpcCantHaveCoroutine()"); + } + + [Test] + public void NetworkBehaviourTargetRpcVoidReturn() + { + HasError("TargetRpcCantHaveNonVoidReturn cannot return a value. Make it void instead", + "System.Int32 WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcVoidReturn.NetworkBehaviourTargetRpcVoidReturn::TargetRpcCantHaveNonVoidReturn()"); + } + + [Test] + public void NetworkBehaviourTargetRpcParamOut() + { + HasError("TargetRpcCantHaveParamOut cannot have out parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamOut.NetworkBehaviourTargetRpcParamOut::TargetRpcCantHaveParamOut(Mirror.NetworkConnection,System.Int32&)"); + } + + [Test] + public void NetworkBehaviourTargetRpcParamOptional() + { + HasError("TargetRpcCantHaveParamOptional cannot have optional parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamOptional.NetworkBehaviourTargetRpcParamOptional::TargetRpcCantHaveParamOptional(Mirror.NetworkConnection,System.Int32)"); + } + + [Test] + public void NetworkBehaviourTargetRpcParamRef() + { + HasError("Cannot pass Int32& by reference", + "System.Int32&"); + HasError("TargetRpcCantHaveParamRef has invalid parameter monkeys", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamRef.NetworkBehaviourTargetRpcParamRef::TargetRpcCantHaveParamRef(Mirror.NetworkConnection,System.Int32&)"); + HasError("Cannot pass type Int32& by reference", + "System.Int32&"); + HasError("TargetRpcCantHaveParamRef has invalid parameter monkeys. Unsupported type System.Int32&, use a supported Mirror type instead", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamRef.NetworkBehaviourTargetRpcParamRef::TargetRpcCantHaveParamRef(Mirror.NetworkConnection,System.Int32&)"); + } + + [Test] + public void NetworkBehaviourTargetRpcParamAbstract() + { + HasError("Cannot generate writer for abstract class AbstractClass. Use a supported type or provide a custom writer", + "WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamAbstract.NetworkBehaviourTargetRpcParamAbstract/AbstractClass"); + // TODO change weaver to run checks for write/read at the same time + //HasError("AbstractClass can't be deserialized because it has no default constructor", + // "WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamAbstract.NetworkBehaviourTargetRpcParamAbstract/AbstractClass"); + } + + [Test] + public void NetworkBehaviourTargetRpcParamComponent() + { + HasError("Cannot generate writer for component type ComponentClass. Use a supported type or provide a custom writer", + "WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent/ComponentClass"); + HasError("TargetRpcCantHaveParamComponent has invalid parameter monkeyComp", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent::TargetRpcCantHaveParamComponent(Mirror.NetworkConnection,WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent/ComponentClass)"); + HasError("Cannot generate reader for component type ComponentClass. Use a supported type or provide a custom reader", + "WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent/ComponentClass"); + HasError("TargetRpcCantHaveParamComponent has invalid parameter monkeyComp. Unsupported type WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent/ComponentClass, use a supported Mirror type instead", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent::TargetRpcCantHaveParamComponent(Mirror.NetworkConnection,WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent.NetworkBehaviourTargetRpcParamComponent/ComponentClass)"); + } + + [Test] + public void NetworkBehaviourClientRpcGenericParam() + { + HasError("RpcCantHaveGeneric cannot have generic parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcGenericParam.NetworkBehaviourClientRpcGenericParam::RpcCantHaveGeneric()"); + } + + [Test] + public void NetworkBehaviourClientRpcCoroutine() + { + HasError("RpcCantHaveCoroutine cannot be a coroutine", + "System.Collections.IEnumerator WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcCoroutine.NetworkBehaviourClientRpcCoroutine::RpcCantHaveCoroutine()"); + } + + [Test] + public void NetworkBehaviourClientRpcVoidReturn() + { + HasError("RpcCantHaveNonVoidReturn cannot return a value. Make it void instead", + "System.Int32 WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcVoidReturn.NetworkBehaviourClientRpcVoidReturn::RpcCantHaveNonVoidReturn()"); + } + + [Test] + public void NetworkBehaviourClientRpcParamOut() + { + HasError("RpcCantHaveParamOut cannot have out parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamOut.NetworkBehaviourClientRpcParamOut::RpcCantHaveParamOut(System.Int32&)"); + } + + [Test] + public void NetworkBehaviourClientRpcParamOptional() + { + HasError("RpcCantHaveParamOptional cannot have optional parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamOptional.NetworkBehaviourClientRpcParamOptional::RpcCantHaveParamOptional(System.Int32)"); + } + + [Test] + public void NetworkBehaviourClientRpcParamRef() + { + HasError("Cannot pass Int32& by reference", + "System.Int32&"); + HasError("RpcCantHaveParamRef has invalid parameter monkeys", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamRef.NetworkBehaviourClientRpcParamRef::RpcCantHaveParamRef(System.Int32&)"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot pass type Int32& by reference", + // "System.Int32&"); + //HasError("RpcCantHaveParamRef has invalid parameter monkeys. Unsupported type System.Int32&, use a supported Mirror type instead", + // "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamRef.NetworkBehaviourClientRpcParamRef::RpcCantHaveParamRef(System.Int32&)"); + } + + [Test] + public void NetworkBehaviourClientRpcParamAbstract() + { + HasError("Cannot generate writer for abstract class AbstractClass. Use a supported type or provide a custom writer", + "WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamAbstract.NetworkBehaviourClientRpcParamAbstract/AbstractClass"); + // TODO change weaver to run checks for write/read at the same time + //HasError("AbstractClass can't be deserialized because it has no default constructor", + // "WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamAbstract.NetworkBehaviourClientRpcParamAbstract/AbstractClass"); + } + + [Test] + public void NetworkBehaviourClientRpcParamComponent() + { + HasError("Cannot generate writer for component type ComponentClass. Use a supported type or provide a custom writer", + "WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent/ComponentClass"); + HasError("RpcCantHaveParamComponent has invalid parameter monkeyComp", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent::RpcCantHaveParamComponent(WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent/ComponentClass)"); + // TODO change weaver to run checks for write/read at the same time + //HasError("Cannot generate reader for component type ComponentClass. Use a supported type or provide a custom reader", + // "WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent/ComponentClass"); + //HasError("RpcCantHaveParamComponent has invalid parameter monkeyComp. Unsupported type WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent/ComponentClass, use a supported Mirror type instead", + // "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent::RpcCantHaveParamComponent(WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent.NetworkBehaviourClientRpcParamComponent/ComponentClass)"); + } + + [Test] + public void NetworkBehaviourClientRpcParamNetworkConnection() + { + HasError("RpcCantHaveParamOptional has invalid parameter monkeyCon. Cannot pass NetworkConnections", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamNetworkConnection.NetworkBehaviourClientRpcParamNetworkConnection::RpcCantHaveParamOptional(Mirror.NetworkConnection)"); + } + + [Test] + public void NetworkBehaviourCmdParamOut() + { + HasError("CmdCantHaveParamOut cannot have out parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamOut.NetworkBehaviourCmdParamOut::CmdCantHaveParamOut(System.Int32&)"); + } + + [Test] + public void NetworkBehaviourCmdParamOptional() + { + HasError("CmdCantHaveParamOptional cannot have optional parameters", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamOptional.NetworkBehaviourCmdParamOptional::CmdCantHaveParamOptional(System.Int32)"); + } + + [Test] + public void NetworkBehaviourCmdParamRef() + { + HasError("Cannot pass Int32& by reference", + "System.Int32&"); + HasError("CmdCantHaveParamRef has invalid parameter monkeys", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamRef.NetworkBehaviourCmdParamRef::CmdCantHaveParamRef(System.Int32&)"); + HasError("Cannot pass type Int32& by reference", + "System.Int32&"); + HasError("CmdCantHaveParamRef has invalid parameter monkeys. Unsupported type System.Int32&, use a supported Mirror type instead", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamRef.NetworkBehaviourCmdParamRef::CmdCantHaveParamRef(System.Int32&)"); + } + + [Test] + public void NetworkBehaviourCmdParamAbstract() + { + HasError("Cannot generate writer for abstract class AbstractClass. Use a supported type or provide a custom writer", + "WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamAbstract.NetworkBehaviourCmdParamAbstract/AbstractClass"); + // TODO change weaver to run checks for write/read at the same time + //HasError("AbstractClass can't be deserialized because it has no default constructor", + // "WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamAbstract.NetworkBehaviourCmdParamAbstract/AbstractClass"); + } + + [Test] + public void NetworkBehaviourCmdParamComponent() + { + HasError("Cannot generate writer for component type ComponentClass. Use a supported type or provide a custom writer", + "WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent/ComponentClass"); + HasError("CmdCantHaveParamComponent has invalid parameter monkeyComp", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent::CmdCantHaveParamComponent(WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent/ComponentClass)"); + HasError("Cannot generate reader for component type ComponentClass. Use a supported type or provide a custom reader", + "WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent/ComponentClass"); + HasError("CmdCantHaveParamComponent has invalid parameter monkeyComp. Unsupported type WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent/ComponentClass, use a supported Mirror type instead", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent::CmdCantHaveParamComponent(WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent.NetworkBehaviourCmdParamComponent/ComponentClass)"); + } + + [Test] + public void NetworkBehaviourCmdParamNetworkConnection() + { + HasError("CmdCantHaveParamOptional has invalid parameter monkeyCon, Cannot pass NetworkConnections. Instead use 'NetworkConnectionToClient conn = null' to get the sender's connection on the server", + "System.Void WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamNetworkConnection.NetworkBehaviourCmdParamNetworkConnection::CmdCantHaveParamOptional(Mirror.NetworkConnection)"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs.meta new file mode 100644 index 000000000..db360898f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f8a452672efda084f8743b29ae9592f7 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid.meta new file mode 100644 index 000000000..f2050a176 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e8c0f412bf9048d7a9992621f6a0d8de +timeCreated: 1643082894 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs new file mode 100644 index 000000000..624e3fe44 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs @@ -0,0 +1,18 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourAbstractBaseValid +{ + public abstract class EntityBase : NetworkBehaviour { } + + public class EntityConcrete : EntityBase + { + [SyncVar] + public int abstractDerivedSync; + } + + public class NetworkBehaviourAbstractBaseValid : EntityConcrete + { + [SyncVar] + public int concreteDerivedSync; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs.meta new file mode 100644 index 000000000..8221d53cb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourAbstractBaseValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1f37afe8878840bd9369afa4d017ba8 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs new file mode 100644 index 000000000..17ef3f9eb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcDuplicateName +{ + class NetworkBehaviourClientRpcDuplicateName : NetworkBehaviour + { + // remote call overloads are now supported + [ClientRpc] + public void RpcWithSameName(int abc) {} + + [ClientRpc] + public void RpcWithSameName(int abc, int def) {} + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs.meta new file mode 100644 index 000000000..aa12a0f8b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourClientRpcDuplicateName.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47702bfe436d8462bb5b937eb08ea120 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs new file mode 100644 index 000000000..f69bb65e5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcDuplicateName +{ + class NetworkBehaviourTargetRpcDuplicateName : NetworkBehaviour + { + // remote call overloads are now supported + [TargetRpc] + public void TargetRpcWithSameName(NetworkConnection monkeyCon, int abc) { } + + [TargetRpc] + public void TargetRpcWithSameName(NetworkConnection monkeyCon, int abc, int def) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs.meta new file mode 100644 index 000000000..1597143f9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcDuplicateName.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b920ec9538e4b42cda6116d5d6238b6b +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs new file mode 100644 index 000000000..9b5559176 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamNetworkConnection +{ + class NetworkBehaviourTargetRpcParamNetworkConnection : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOptional(NetworkConnection monkeyCon) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs.meta new file mode 100644 index 000000000..d61ef2a66 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourTargetRpcParamNetworkConnection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a608f5852eaf44fba9f1f855abc2a3ec +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs new file mode 100644 index 000000000..bb9f6c975 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourValid +{ + class MirrorTestPlayer : NetworkBehaviour + { + [SyncVar] + public int durpatron9000 = 12; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs.meta new file mode 100644 index 000000000..db4c8619d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests_IsValid/NetworkBehaviourValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2b7fdf376724a439f9524301a442e6f3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcCoroutine.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcCoroutine.cs new file mode 100644 index 000000000..0434b94fe --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcCoroutine.cs @@ -0,0 +1,14 @@ +using System.Collections; +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcCoroutine +{ + class NetworkBehaviourClientRpcCoroutine : NetworkBehaviour + { + [ClientRpc] + public IEnumerator RpcCantHaveCoroutine() + { + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcGenericParam.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcGenericParam.cs new file mode 100644 index 000000000..5cef7dc41 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcGenericParam.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcGenericParam +{ + class NetworkBehaviourClientRpcGenericParam : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveGeneric() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamAbstract.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamAbstract.cs new file mode 100644 index 000000000..a5c51de73 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamAbstract.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamAbstract +{ + class NetworkBehaviourClientRpcParamAbstract : NetworkBehaviour + { + public abstract class AbstractClass + { + int monkeys = 12; + } + + [ClientRpc] + public void RpcCantHaveParamAbstract(AbstractClass monkeys) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamComponent.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamComponent.cs new file mode 100644 index 000000000..1208e3822 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamComponent.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamComponent +{ + class NetworkBehaviourClientRpcParamComponent : NetworkBehaviour + { + public class ComponentClass : UnityEngine.Component + { + int monkeys = 12; + } + + [ClientRpc] + public void RpcCantHaveParamComponent(ComponentClass monkeyComp) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs new file mode 100644 index 000000000..912c2f2ee --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamNetworkConnection +{ + class NetworkBehaviourClientRpcParamNetworkConnection : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamOptional(NetworkConnection monkeyCon) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOptional.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOptional.cs new file mode 100644 index 000000000..03215101c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOptional.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamOptional +{ + class NetworkBehaviourClientRpcParamOptional : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamOptional(int monkeys = 12) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOut.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOut.cs new file mode 100644 index 000000000..7225adb0b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamOut.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamOut +{ + class NetworkBehaviourClientRpcParamOut : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamOut(out int monkeys) + { + monkeys = 12; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamRef.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamRef.cs new file mode 100644 index 000000000..3de256360 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcParamRef.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcParamRef +{ + class NetworkBehaviourClientRpcParamRef : NetworkBehaviour + { + [ClientRpc] + public void RpcCantHaveParamRef(ref int monkeys) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcVoidReturn.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcVoidReturn.cs new file mode 100644 index 000000000..75d31847a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourClientRpcVoidReturn.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourClientRpcVoidReturn +{ + class NetworkBehaviourClientRpcVoidReturn : NetworkBehaviour + { + [ClientRpc] + public int RpcCantHaveNonVoidReturn() + { + return 1; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdCoroutine.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdCoroutine.cs new file mode 100644 index 000000000..39bf66076 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdCoroutine.cs @@ -0,0 +1,14 @@ +using System.Collections; +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdCoroutine +{ + class NetworkBehaviourCmdCoroutine : NetworkBehaviour + { + [Command] + public IEnumerator CmdCantHaveCoroutine() + { + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdDuplicateName.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdDuplicateName.cs new file mode 100644 index 000000000..acb8c78c3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdDuplicateName.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdDuplicateName +{ + class NetworkBehaviourCmdDuplicateName : NetworkBehaviour + { + // remote call overloads are now supported + [Command] + public void CmdWithSameName(int abc) { } + + [Command] + public void CmdWithSameName(int abc, int def) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdGenericParam.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdGenericParam.cs new file mode 100644 index 000000000..2e10933f5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdGenericParam.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdGenericParam +{ + class NetworkBehaviourCmdGenericParam : NetworkBehaviour + { + [Command] + public void CmdCantHaveGeneric() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamAbstract.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamAbstract.cs new file mode 100644 index 000000000..d6bac6044 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamAbstract.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamAbstract +{ + class NetworkBehaviourCmdParamAbstract : NetworkBehaviour + { + public abstract class AbstractClass + { + int monkeys = 12; + } + + [Command] + public void CmdCantHaveParamAbstract(AbstractClass monkeys) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamComponent.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamComponent.cs new file mode 100644 index 000000000..e39f8a313 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamComponent.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamComponent +{ + class NetworkBehaviourCmdParamComponent : NetworkBehaviour + { + public class ComponentClass : UnityEngine.Component + { + int monkeys = 12; + } + + [Command] + public void CmdCantHaveParamComponent(ComponentClass monkeyComp) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamNetworkConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamNetworkConnection.cs new file mode 100644 index 000000000..9ad198a1b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamNetworkConnection +{ + class NetworkBehaviourCmdParamNetworkConnection : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamOptional(NetworkConnection monkeyCon) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOptional.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOptional.cs new file mode 100644 index 000000000..509715d2a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOptional.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamOptional +{ + class NetworkBehaviourCmdParamOptional : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamOptional(int monkeys = 12) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOut.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOut.cs new file mode 100644 index 000000000..2999339fe --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamOut.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamOut +{ + class NetworkBehaviourCmdParamOut : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamOut(out int monkeys) + { + monkeys = 12; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamRef.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamRef.cs new file mode 100644 index 000000000..54fd0d0e4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdParamRef.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdParamRef +{ + class NetworkBehaviourCmdParamRef : NetworkBehaviour + { + [Command] + public void CmdCantHaveParamRef(ref int monkeys) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdVoidReturn.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdVoidReturn.cs new file mode 100644 index 000000000..1015efdee --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourCmdVoidReturn.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourCmdVoidReturn +{ + class NetworkBehaviourCmdVoidReturn : NetworkBehaviour + { + [Command] + public int CmdCantHaveNonVoidReturn() + { + return 1; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs new file mode 100644 index 000000000..c6deba0bb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourGeneric.cs @@ -0,0 +1,9 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourGeneric +{ + class NetworkBehaviourGeneric : NetworkBehaviour + { + T genericsNotAllowed; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcCoroutine.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcCoroutine.cs new file mode 100644 index 000000000..dbd8c13fe --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcCoroutine.cs @@ -0,0 +1,14 @@ +using System.Collections; +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcCoroutine +{ + class NetworkBehaviourTargetRpcCoroutine : NetworkBehaviour + { + [TargetRpc] + public IEnumerator TargetRpcCantHaveCoroutine() + { + yield return null; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcGenericParam.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcGenericParam.cs new file mode 100644 index 000000000..c37431d67 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcGenericParam.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcGenericParam +{ + class NetworkBehaviourTargetRpcGenericParam : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveGeneric() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamAbstract.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamAbstract.cs new file mode 100644 index 000000000..fb8938fb3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamAbstract.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamAbstract +{ + class NetworkBehaviourTargetRpcParamAbstract : NetworkBehaviour + { + public abstract class AbstractClass + { + int monkeys = 12; + } + + [TargetRpc] + public void TargetRpcCantHaveParamAbstract(NetworkConnection monkeyCon, AbstractClass monkeys) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamComponent.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamComponent.cs new file mode 100644 index 000000000..b2287761d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamComponent.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamComponent +{ + class NetworkBehaviourTargetRpcParamComponent : NetworkBehaviour + { + public class ComponentClass : UnityEngine.Component + { + int monkeys = 12; + } + + [TargetRpc] + public void TargetRpcCantHaveParamComponent(NetworkConnection monkeyCon, ComponentClass monkeyComp) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs new file mode 100644 index 000000000..38466a3b8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst +{ + class NetworkBehaviourTargetRpcParamNetworkConnectionNotFirst : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOptional(int abc, NetworkConnection monkeyCon) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOptional.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOptional.cs new file mode 100644 index 000000000..ea4655c52 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOptional.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamOptional +{ + class NetworkBehaviourTargetRpcParamOptional : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOptional(NetworkConnection monkeyCon, int monkeys = 12) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOut.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOut.cs new file mode 100644 index 000000000..0892aa1a7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamOut.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamOut +{ + class NetworkBehaviourTargetRpcParamOut : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamOut(NetworkConnection monkeyCon, out int monkeys) + { + monkeys = 12; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamRef.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamRef.cs new file mode 100644 index 000000000..3279878a0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcParamRef.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcParamRef +{ + class NetworkBehaviourTargetRpcParamRef : NetworkBehaviour + { + [TargetRpc] + public void TargetRpcCantHaveParamRef(NetworkConnection monkeyCon, ref int monkeys) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcVoidReturn.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcVoidReturn.cs new file mode 100644 index 000000000..e44356aa1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverNetworkBehaviourTests~/NetworkBehaviourTargetRpcVoidReturn.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverNetworkBehaviourTests.NetworkBehaviourTargetRpcVoidReturn +{ + class NetworkBehaviourTargetRpcVoidReturn : NetworkBehaviour + { + [TargetRpc] + public int TargetRpcCantHaveNonVoidReturn() + { + return 1; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs new file mode 100644 index 000000000..e7f6cd7c4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs @@ -0,0 +1,26 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + // Some tests for SyncObjects are in WeaverSyncListTests and apply to SyncDictionary too + public class WeaverSyncDictionaryTests : WeaverTestsBuildFromTestName + { + [Test] + public void SyncDictionaryErrorForGenericStructKey() + { + HasError("Cannot generate reader for generic variable MyGenericStruct`1. Use a supported type or provide a custom reader", + "WeaverSyncDictionaryTests.SyncDictionaryErrorForGenericStructKey.SyncDictionaryErrorForGenericStructKey/MyGenericStruct`1"); + HasError("Cannot generate writer for generic type MyGenericStruct`1. Use a supported type or provide a custom writer", + "WeaverSyncDictionaryTests.SyncDictionaryErrorForGenericStructKey.SyncDictionaryErrorForGenericStructKey/MyGenericStruct`1"); + } + + [Test] + public void SyncDictionaryErrorForGenericStructItem() + { + HasError("Cannot generate reader for generic variable MyGenericStruct`1. Use a supported type or provide a custom reader", + "WeaverSyncDictionaryTests.SyncDictionaryErrorForGenericStructItem.SyncDictionaryErrorForGenericStructItem/MyGenericStruct`1"); + HasError("Cannot generate writer for generic type MyGenericStruct`1. Use a supported type or provide a custom writer", + "WeaverSyncDictionaryTests.SyncDictionaryErrorForGenericStructItem.SyncDictionaryErrorForGenericStructItem/MyGenericStruct`1"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs.meta new file mode 100644 index 000000000..a96f8c071 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: de75571c46d27c34bb2c6a67d5eac558 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess.meta new file mode 100644 index 000000000..e47cffdeb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 213434594cfc42e6a93f05a7d16f15fd +timeCreated: 1643083057 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs new file mode 100644 index 000000000..1b85dd439 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs @@ -0,0 +1,11 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.GenericSyncDictionaryCanBeUsed +{ + class GenericSyncDictionaryCanBeUsed : NetworkBehaviour + { + readonly SomeSyncDictionary someDictionary; + + public class SomeSyncDictionary : SyncDictionary { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs.meta new file mode 100644 index 000000000..16e37dfaf --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/GenericSyncDictionaryCanBeUsed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b80f4132d92cf42dfbf75cad97d84513 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs new file mode 100644 index 000000000..0c1e03177 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionary +{ + class SyncDictionaryValid : NetworkBehaviour + { + public class SyncDictionaryIntString : SyncDictionary { } + + public readonly SyncDictionaryIntString Foo; + } + + +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs.meta new file mode 100644 index 000000000..32b959aba --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionary.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b5d1ec150fb64234b3cdbae16ebb12f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs new file mode 100644 index 000000000..fd62e9643 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryGenericAbstractInheritance +{ + class SyncDictionaryGenericAbstractInheritance : NetworkBehaviour + { + readonly SomeDictionaryIntString dictionary = new SomeDictionaryIntString(); + + public abstract class SomeDictionary : SyncDictionary { } + + public class SomeDictionaryIntString : SomeDictionary { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs.meta new file mode 100644 index 000000000..ac85cbc65 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericAbstractInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: e951e9979768745aeb9a6dcf260dd20c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs new file mode 100644 index 000000000..47778b85a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryGenericInheritance +{ + class SyncDictionaryGenericInheritance : NetworkBehaviour + { + readonly SomeDictionaryIntString dictionary = new SomeDictionaryIntString(); + + public class SomeDictionary : SyncDictionary { } + + public class SomeDictionaryIntString : SomeDictionary { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs.meta new file mode 100644 index 000000000..617a87018 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6b7161b20ae9041b6a5dc17074967813 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs new file mode 100644 index 000000000..049ee8f83 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs @@ -0,0 +1,27 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryGenericStructItemWithCustomMethods +{ + class SyncDictionaryGenericStructItemWithCustomMethods : NetworkBehaviour + { + readonly SyncDictionary> harpseals; + } + + public struct MyGenericStruct + { + public T genericpotato; + } + + public static class MyGenericStructDictionary + { + public static void WriteItem(this NetworkWriter writer, MyGenericStruct item) + { + writer.WriteFloat(item.genericpotato); + } + + public static MyGenericStruct ReadItem(this NetworkReader reader) + { + return new MyGenericStruct() { genericpotato = reader.ReadFloat() }; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs.meta new file mode 100644 index 000000000..b6db60497 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructItemWithCustomMethods.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4eb89b9d2c0db4ebd9e6f8f4bc5d4f24 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs new file mode 100644 index 000000000..a5942f22e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs @@ -0,0 +1,27 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryGenericStructKeyWithCustomMethods +{ + class SyncDictionaryGenericStructKeyWithCustomMethods : NetworkBehaviour + { + readonly SyncDictionary, int> harpseals; + } + + public struct MyGenericStruct + { + public T genericpotato; + } + + public static class MyGenericStructDictionary + { + public static void WriteKey(this NetworkWriter writer, MyGenericStruct item) + { + writer.WriteFloat(item.genericpotato); + } + + public static MyGenericStruct ReadKey(this NetworkReader reader) + { + return new MyGenericStruct() { genericpotato = reader.ReadFloat() }; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs.meta new file mode 100644 index 000000000..6e5e17258 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryGenericStructKeyWithCustomMethods.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ce03c1e3290a94e2390a4bf1ec2982d6 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs new file mode 100644 index 000000000..6d2f207fe --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryInheritance +{ + class SyncDictionaryInheritance : NetworkBehaviour + { + readonly SuperDictionary dictionary = new SuperDictionary(); + + public class SomeDictionary : SyncDictionary { } + + public class SomeDictionaryIntString : SomeDictionary { } + + public class SuperDictionary : SomeDictionaryIntString + { + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs.meta new file mode 100644 index 000000000..8ff9429c3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 1cdebedceef1e4dbb8438b4d9ef22d01 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs new file mode 100644 index 000000000..8a63b53fe --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryStructItem +{ + class SyncDictionaryStructItem : NetworkBehaviour + { + readonly MyStructDictionary Foo; + + struct MyStruct + { + public int potato; + public float floatingpotato; + public double givemetwopotatoes; + } + class MyStructDictionary : SyncDictionary { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs.meta new file mode 100644 index 000000000..840061b39 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructItem.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d83c6882b2bef4a37a9592f6895a14f1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs new file mode 100644 index 000000000..7476a228d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryStructKey +{ + class SyncDictionaryStructKey : NetworkBehaviour + { + readonly MyStructDictionary Foo; + + struct MyStruct + { + public int potato; + public float floatingpotato; + public double givemetwopotatoes; + } + class MyStructDictionary : SyncDictionary { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs.meta new file mode 100644 index 000000000..e23b74cc3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests_IsSuccess/SyncDictionaryStructKey.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c1923cdb541a94e9dad0b19cfcd53ea4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructItem.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructItem.cs new file mode 100644 index 000000000..75e80b81d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructItem.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryErrorForGenericStructItem +{ + class SyncDictionaryErrorForGenericStructItem : NetworkBehaviour + { + struct MyGenericStruct + { + T genericpotato; + } + + class MyGenericStructDictionary : SyncDictionary> { }; + + readonly MyGenericStructDictionary harpseals; + } + +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructKey.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructKey.cs new file mode 100644 index 000000000..9b3b4bfd9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncDictionaryTests~/SyncDictionaryErrorForGenericStructKey.cs @@ -0,0 +1,16 @@ +using Mirror; + +namespace WeaverSyncDictionaryTests.SyncDictionaryErrorForGenericStructKey +{ + class SyncDictionaryErrorForGenericStructKey : NetworkBehaviour + { + readonly MyGenericStructDictionary harpseals; + + struct MyGenericStruct + { + T genericpotato; + } + + class MyGenericStructDictionary : SyncDictionary, int> { }; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs new file mode 100644 index 000000000..d6326e6c1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs @@ -0,0 +1,65 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverSyncListTests : WeaverTestsBuildFromTestName + { + [Test] + public void SyncListNestedInAbstractClassWithInvalid() + { + // we need this negative test to make sure that SyncList is being processed + HasError("Cannot generate reader for Object. Use a supported type or provide a custom reader", + "UnityEngine.Object"); + HasError("target has an unsupported type", + "UnityEngine.Object WeaverSyncListTests.SyncListNestedInAbstractClassWithInvalid.SyncListNestedStructWithInvalid/SomeAbstractClass/MyNestedStruct::target"); + HasError("Cannot generate writer for Object. Use a supported type or provide a custom writer", + "UnityEngine.Object"); + } + + [Test] + public void SyncListNestedInStructWithInvalid() + { + // we need this negative test to make sure that SyncList is being processed + HasError("Cannot generate reader for Object. Use a supported type or provide a custom reader", + "UnityEngine.Object"); + HasError("target has an unsupported type", + "UnityEngine.Object WeaverSyncListTests.SyncListNestedInStructWithInvalid.SyncListNestedInStructWithInvalid/SomeData::target"); + HasError("Cannot generate writer for Object. Use a supported type or provide a custom writer", + "UnityEngine.Object"); + } + + [Test] + public void SyncListErrorForGenericStruct() + { + HasError("Cannot generate reader for generic variable MyGenericStruct`1. Use a supported type or provide a custom reader", + "WeaverSyncListTests.SyncListErrorForGenericStruct.SyncListErrorForGenericStruct/MyGenericStruct`1"); + HasError("Cannot generate writer for generic type MyGenericStruct`1. Use a supported type or provide a custom writer", + "WeaverSyncListTests.SyncListErrorForGenericStruct.SyncListErrorForGenericStruct/MyGenericStruct`1"); + } + + // IsSuccess test, but still in here because it shows an error + // if we move to regular C# + [Test] + public void SyncListGenericStructWithCustomMethods() + { + IsSuccess(); + } + + [Test] + public void SyncListErrorForInterface() + { + HasError("Cannot generate reader for interface MyInterface. Use a supported type or provide a custom reader", + "WeaverSyncListTests.SyncListErrorForInterface.MyInterface"); + HasError("Cannot generate writer for interface MyInterface. Use a supported type or provide a custom writer", + "WeaverSyncListTests.SyncListErrorForInterface.MyInterface"); + } + + // IsSuccess test, but still in here because it shows an error + // if we move to regular C# + [Test] + public void SyncListInterfaceWithCustomMethods() + { + IsSuccess(); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs.meta new file mode 100644 index 000000000..4fff813e9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 40a07120cf158ca4e98a01d79c7b48e0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess.meta new file mode 100644 index 000000000..16fca91c4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b5dd2567fffc48f9a1ebdf53819aa513 +timeCreated: 1643083178 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs new file mode 100644 index 000000000..86be36c42 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs @@ -0,0 +1,12 @@ +using Mirror; + +namespace WeaverSyncListTests.GenericSyncListCanBeUsed +{ + class GenericSyncListCanBeUsed : NetworkBehaviour + { + readonly SomeList someList; + + + public class SomeList : SyncList { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs.meta new file mode 100644 index 000000000..21cb0cf80 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/GenericSyncListCanBeUsed.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 42f9700f4998548c498565f7b55edbb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs new file mode 100644 index 000000000..174d28803 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs @@ -0,0 +1,9 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncList +{ + class SyncList : NetworkBehaviour + { + public readonly SyncList Foo; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs.meta new file mode 100644 index 000000000..cc1414223 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncList.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2de457dfaaa35451fa002b19623ea678 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs new file mode 100644 index 000000000..c073ca875 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs @@ -0,0 +1,11 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListByteValid +{ + class SyncListByteValid : NetworkBehaviour + { + class MyByteClass : SyncList { }; + + readonly MyByteClass Foo; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs.meta new file mode 100644 index 000000000..44398f4b5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListByteValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 639ccd9a8ac3e4d669b11291a9d0cf39 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs new file mode 100644 index 000000000..8821f81aa --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListGenericAbstractInheritance +{ + class SyncListGenericAbstractInheritance : NetworkBehaviour + { + readonly SomeListInt superSyncListString = new SomeListInt(); + + + public abstract class SomeList : SyncList { } + + public class SomeListInt : SomeList { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs.meta new file mode 100644 index 000000000..f58da9aae --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericAbstractInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ac1eb2763b78a442d82c1e5c6ec06cbb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs new file mode 100644 index 000000000..5b1a56f86 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListGenericInheritance +{ + class SyncListGenericInheritance : NetworkBehaviour + { + readonly SomeListInt someList = new SomeListInt(); + + + public class SomeList : SyncList { } + + public class SomeListInt : SomeList { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs.meta new file mode 100644 index 000000000..2851a19e9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a6efc238f8074df3b8c0628c49e9b23 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs new file mode 100644 index 000000000..c23619f51 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListGenericInheritanceWithMultipleGeneric +{ + /* + This test should pass + */ + class SyncListGenericInheritanceWithMultipleGeneric : NetworkBehaviour + { + readonly SomeListInt someList = new SomeListInt(); + + + public class SomeList : SyncList { } + + public class SomeListInt : SomeList { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs.meta new file mode 100644 index 000000000..207808c45 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListGenericInheritanceWithMultipleGeneric.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ebef79e4b70014d799f0ccb14b544dca +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs new file mode 100644 index 000000000..4f92edd86 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListInheritance +{ + class SyncListInheritance : NetworkBehaviour + { + readonly SuperSyncList superSyncListString = new SuperSyncList(); + + + public class SuperSyncList : SyncList + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs.meta new file mode 100644 index 000000000..55a08e4da --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d98ef375fb0614946906b2f438a43d82 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs new file mode 100644 index 000000000..e410c166a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListMissingParamlessCtor +{ + class SyncListMissingParamlessCtor : NetworkBehaviour + { + public readonly SyncListString2 Foo; + + public class SyncListString2 : SyncList + { + public SyncListString2(int phooey) { } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs.meta new file mode 100644 index 000000000..50fd94e17 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtor.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 57b22f7b42094409a9a1158aabad83d0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs new file mode 100644 index 000000000..b16cd20d1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListMissingParamlessCtorManuallyInitialized +{ + class SyncListMissingParamlessCtorManuallyInitialized : NetworkBehaviour + { + public readonly SyncListString2 Foo = new SyncListString2(20); + + public class SyncListString2 : SyncList + { + public SyncListString2(int phooey) { } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs.meta new file mode 100644 index 000000000..046be8783 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListMissingParamlessCtorManuallyInitialized.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 762689087c636418e8e261eef542dcb3 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs new file mode 100644 index 000000000..8d03d58dc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs @@ -0,0 +1,20 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListNestedInAbstractClass +{ + class SyncListNestedStruct : NetworkBehaviour + { + readonly SomeAbstractClass.MyNestedStructList Foo; + + public abstract class SomeAbstractClass + { + public struct MyNestedStruct + { + public int potato; + public float floatingpotato; + public double givemetwopotatoes; + } + public class MyNestedStructList : SyncList { } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs.meta new file mode 100644 index 000000000..1e38710ef --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInAbstractClass.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 47ed2f59dc03d417ab513677ae404a18 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs new file mode 100644 index 000000000..e611f9ba6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListNestedInStruct +{ + class SyncListNestedStruct : NetworkBehaviour + { + readonly SomeData.SyncList Foo; + + public struct SomeData + { + public int usefulNumber; + public class SyncList : Mirror.SyncList { } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs.meta new file mode 100644 index 000000000..038d182de --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedInStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 18f2e8d5c893344259ac8b2e17cb6d3f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs new file mode 100644 index 000000000..da834ad7b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListNestedStruct +{ + class SyncListNestedStruct : NetworkBehaviour + { + readonly MyNestedStructList Foo; + + struct MyNestedStruct + { + public int potato; + public float floatingpotato; + public double givemetwopotatoes; + } + class MyNestedStructList : SyncList { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs.meta new file mode 100644 index 000000000..9107dbc39 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListNestedStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 6a60a4daf5b784bafadc07ffc475b596 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs new file mode 100644 index 000000000..82cd68988 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListStruct +{ + class SyncListStruct : NetworkBehaviour + { + readonly MyStructList Foo; + + struct MyStruct + { + public int potato; + public float floatingpotato; + public double givemetwopotatoes; + } + class MyStructList : SyncList { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs.meta new file mode 100644 index 000000000..e26869672 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests_IsSuccess/SyncListStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 0bfa51174087b486ebb97cf94dc702a4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForGenericStruct.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForGenericStruct.cs new file mode 100644 index 000000000..3809172b4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForGenericStruct.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListErrorForGenericStruct +{ + class SyncListErrorForGenericStruct : NetworkBehaviour + { + MyGenericStructList harpseals; + + + struct MyGenericStruct + { + T genericpotato; + } + + class MyGenericStructList : SyncList> { }; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForInterface.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForInterface.cs new file mode 100644 index 000000000..2e059b324 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListErrorForInterface.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListErrorForInterface +{ + class SyncListErrorForInterface : NetworkBehaviour + { + MyInterfaceList Foo; + } + interface MyInterface + { + int someNumber { get; set; } + } + class MyInterfaceList : SyncList { } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListGenericStructWithCustomMethods.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListGenericStructWithCustomMethods.cs new file mode 100644 index 000000000..8273b4f61 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListGenericStructWithCustomMethods.cs @@ -0,0 +1,27 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListGenericStructWithCustomMethods +{ + class SyncListGenericStructWithCustomMethods : NetworkBehaviour + { + readonly SyncList> harpseals; + } + + struct MyGenericStruct + { + public T genericpotato; + } + + static class MyGenericStructList + { + static void SerializeItem(this NetworkWriter writer, MyGenericStruct item) + { + writer.WriteFloat(item.genericpotato); + } + + static MyGenericStruct DeserializeItem(this NetworkReader reader) + { + return new MyGenericStruct() { genericpotato = reader.ReadFloat() }; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListInterfaceWithCustomMethods.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListInterfaceWithCustomMethods.cs new file mode 100644 index 000000000..d94ffd9dc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListInterfaceWithCustomMethods.cs @@ -0,0 +1,31 @@ +using Mirror; + +namespace WeaverSyncListTests.SyncListInterfaceWithCustomMethods +{ + class SyncListInterfaceWithCustomMethods : NetworkBehaviour + { + readonly SyncList Foo; + } + + interface IMyInterface + { + int someNumber { get; set; } + } + + class MyUser : IMyInterface + { + public int someNumber { get; set; } + } + + static class MyInterfaceList + { + static void SerializeItem(this NetworkWriter writer, IMyInterface item) + { + writer.WriteInt(item.someNumber); + } + static IMyInterface DeserializeItem(this NetworkReader reader) + { + return new MyUser { someNumber = reader.ReadInt() }; + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInAbstractClassWithInvalid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInAbstractClassWithInvalid.cs new file mode 100644 index 000000000..ed6566317 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInAbstractClassWithInvalid.cs @@ -0,0 +1,20 @@ +using Mirror; +using UnityEngine; + +namespace WeaverSyncListTests.SyncListNestedInAbstractClassWithInvalid +{ + class SyncListNestedStructWithInvalid : NetworkBehaviour + { + readonly SomeAbstractClass.MyNestedStructList Foo; + + public abstract class SomeAbstractClass + { + public struct MyNestedStruct + { + public int potato; + public Object target; + } + public class MyNestedStructList : SyncList { } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInStructWithInvalid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInStructWithInvalid.cs new file mode 100644 index 000000000..16c8c633e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncListTests~/SyncListNestedInStructWithInvalid.cs @@ -0,0 +1,18 @@ +using Mirror; +using UnityEngine; + +namespace WeaverSyncListTests.SyncListNestedInStructWithInvalid +{ + class SyncListNestedInStructWithInvalid : NetworkBehaviour + { + readonly SomeData.SyncList Foo; + + public struct SomeData + { + public int usefulNumber; + public Object target; + + public class SyncList : Mirror.SyncList { } + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs new file mode 100644 index 000000000..b9de424b0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs @@ -0,0 +1,21 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverSyncObjectsTests : WeaverTestsBuildFromTestName + { + [Test] + public void SyncObjectsMoreThanMax() + { + HasError("SyncObjectsMoreThanMax has > 64 SyncObjects (SyncLists etc). Consider refactoring your class into multiple components", + "WeaverSyncObjectsTest.SyncObjectsMoreThanMax.SyncObjectsMoreThanMax"); + } + + [Test] + public void RecommendsReadonly() + { + HasWarning("list should have a 'readonly' keyword in front of the variable because Mirror.SyncObjects always need to be initialized by the Weaver.", + "Mirror.SyncList`1 WeaverSyncObjectsTest.SyncObjectsMoreThanMax.RecommendsReadonly::list"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs.meta new file mode 100644 index 000000000..dfafec92d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 1aef13e7dbbc42df816347e4fb1ae8fd +timeCreated: 1631798559 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess.meta new file mode 100644 index 000000000..af83855bc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: b6889a5a83d34ab1b7cec95d6d437efd +timeCreated: 1643083530 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs new file mode 100644 index 000000000..a7b5e7cc5 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs @@ -0,0 +1,80 @@ +using Mirror; + +namespace WeaverSyncObjectsTest.SyncObjectsExactlyMax +{ + // SyncObjects mask is 64 bit. exactly 64 SyncObjects needs to work. + public class SyncObjectsExactlyMax : NetworkBehaviour + { + // from 1..64 + public readonly SyncList list1 = new SyncList(); + public readonly SyncList list2 = new SyncList(); + public readonly SyncList list3 = new SyncList(); + public readonly SyncList list4 = new SyncList(); + public readonly SyncList list5 = new SyncList(); + public readonly SyncList list6 = new SyncList(); + public readonly SyncList list7 = new SyncList(); + public readonly SyncList list8 = new SyncList(); + public readonly SyncList list9 = new SyncList(); + + public readonly SyncList list10 = new SyncList(); + public readonly SyncList list11 = new SyncList(); + public readonly SyncList list12 = new SyncList(); + public readonly SyncList list13 = new SyncList(); + public readonly SyncList list14 = new SyncList(); + public readonly SyncList list15 = new SyncList(); + public readonly SyncList list16 = new SyncList(); + public readonly SyncList list17 = new SyncList(); + public readonly SyncList list18 = new SyncList(); + public readonly SyncList list19 = new SyncList(); + + public readonly SyncList list20 = new SyncList(); + public readonly SyncList list21 = new SyncList(); + public readonly SyncList list22 = new SyncList(); + public readonly SyncList list23 = new SyncList(); + public readonly SyncList list24 = new SyncList(); + public readonly SyncList list25 = new SyncList(); + public readonly SyncList list26 = new SyncList(); + public readonly SyncList list27 = new SyncList(); + public readonly SyncList list28 = new SyncList(); + public readonly SyncList list29 = new SyncList(); + + public readonly SyncList list30 = new SyncList(); + public readonly SyncList list31 = new SyncList(); + public readonly SyncList list32 = new SyncList(); + public readonly SyncList list33 = new SyncList(); + public readonly SyncList list34 = new SyncList(); + public readonly SyncList list35 = new SyncList(); + public readonly SyncList list36 = new SyncList(); + public readonly SyncList list37 = new SyncList(); + public readonly SyncList list38 = new SyncList(); + public readonly SyncList list39 = new SyncList(); + + public readonly SyncList list40 = new SyncList(); + public readonly SyncList list41 = new SyncList(); + public readonly SyncList list42 = new SyncList(); + public readonly SyncList list43 = new SyncList(); + public readonly SyncList list44 = new SyncList(); + public readonly SyncList list45 = new SyncList(); + public readonly SyncList list46 = new SyncList(); + public readonly SyncList list47 = new SyncList(); + public readonly SyncList list48 = new SyncList(); + public readonly SyncList list49 = new SyncList(); + + public readonly SyncList list50 = new SyncList(); + public readonly SyncList list51 = new SyncList(); + public readonly SyncList list52 = new SyncList(); + public readonly SyncList list53 = new SyncList(); + public readonly SyncList list54 = new SyncList(); + public readonly SyncList list55 = new SyncList(); + public readonly SyncList list56 = new SyncList(); + public readonly SyncList list57 = new SyncList(); + public readonly SyncList list58 = new SyncList(); + public readonly SyncList list59 = new SyncList(); + + public readonly SyncList list60 = new SyncList(); + public readonly SyncList list61 = new SyncList(); + public readonly SyncList list62 = new SyncList(); + public readonly SyncList list63 = new SyncList(); + public readonly SyncList list64 = new SyncList(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs.meta new file mode 100644 index 000000000..85d7b4e00 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests_IsSuccess/SyncObjectsExactlyMax.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9013959e50bc44e9b2b691b85f61bd55 +timeCreated: 1631799264 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs new file mode 100644 index 000000000..477dd9ed7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverSyncObjectsTest.SyncObjectsMoreThanMax +{ + public class RecommendsReadonly : NetworkBehaviour + { + // NOT readonly. should show weaver recommendation. + public SyncList list = new SyncList(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs.meta new file mode 100644 index 000000000..be1b1d59b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/RecommendsReadonly.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 8d7133c3dad54d3dbe76f2f8be0803ec +timeCreated: 1632459283 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs new file mode 100644 index 000000000..7e2093b0d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs @@ -0,0 +1,81 @@ +using Mirror; + +namespace WeaverSyncObjectsTest.SyncObjectsMoreThanMax +{ + // SyncObjects mask is 64 bit. we can't have more than 64 SyncObjects. + public class SyncObjectsMoreThanMax : NetworkBehaviour + { + // from 1..65 + public readonly SyncList list1 = new SyncList(); + public readonly SyncList list2 = new SyncList(); + public readonly SyncList list3 = new SyncList(); + public readonly SyncList list4 = new SyncList(); + public readonly SyncList list5 = new SyncList(); + public readonly SyncList list6 = new SyncList(); + public readonly SyncList list7 = new SyncList(); + public readonly SyncList list8 = new SyncList(); + public readonly SyncList list9 = new SyncList(); + + public readonly SyncList list10 = new SyncList(); + public readonly SyncList list11 = new SyncList(); + public readonly SyncList list12 = new SyncList(); + public readonly SyncList list13 = new SyncList(); + public readonly SyncList list14 = new SyncList(); + public readonly SyncList list15 = new SyncList(); + public readonly SyncList list16 = new SyncList(); + public readonly SyncList list17 = new SyncList(); + public readonly SyncList list18 = new SyncList(); + public readonly SyncList list19 = new SyncList(); + + public readonly SyncList list20 = new SyncList(); + public readonly SyncList list21 = new SyncList(); + public readonly SyncList list22 = new SyncList(); + public readonly SyncList list23 = new SyncList(); + public readonly SyncList list24 = new SyncList(); + public readonly SyncList list25 = new SyncList(); + public readonly SyncList list26 = new SyncList(); + public readonly SyncList list27 = new SyncList(); + public readonly SyncList list28 = new SyncList(); + public readonly SyncList list29 = new SyncList(); + + public readonly SyncList list30 = new SyncList(); + public readonly SyncList list31 = new SyncList(); + public readonly SyncList list32 = new SyncList(); + public readonly SyncList list33 = new SyncList(); + public readonly SyncList list34 = new SyncList(); + public readonly SyncList list35 = new SyncList(); + public readonly SyncList list36 = new SyncList(); + public readonly SyncList list37 = new SyncList(); + public readonly SyncList list38 = new SyncList(); + public readonly SyncList list39 = new SyncList(); + + public readonly SyncList list40 = new SyncList(); + public readonly SyncList list41 = new SyncList(); + public readonly SyncList list42 = new SyncList(); + public readonly SyncList list43 = new SyncList(); + public readonly SyncList list44 = new SyncList(); + public readonly SyncList list45 = new SyncList(); + public readonly SyncList list46 = new SyncList(); + public readonly SyncList list47 = new SyncList(); + public readonly SyncList list48 = new SyncList(); + public readonly SyncList list49 = new SyncList(); + + public readonly SyncList list50 = new SyncList(); + public readonly SyncList list51 = new SyncList(); + public readonly SyncList list52 = new SyncList(); + public readonly SyncList list53 = new SyncList(); + public readonly SyncList list54 = new SyncList(); + public readonly SyncList list55 = new SyncList(); + public readonly SyncList list56 = new SyncList(); + public readonly SyncList list57 = new SyncList(); + public readonly SyncList list58 = new SyncList(); + public readonly SyncList list59 = new SyncList(); + + public readonly SyncList list60 = new SyncList(); + public readonly SyncList list61 = new SyncList(); + public readonly SyncList list62 = new SyncList(); + public readonly SyncList list63 = new SyncList(); + public readonly SyncList list64 = new SyncList(); + public readonly SyncList list65 = new SyncList(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs.meta new file mode 100644 index 000000000..e44a2f523 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncObjectsTests~/SyncObjectsMoreThanMax.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 5f8ac61059614330bdcf0f9305c53136 +timeCreated: 1631798423 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess.meta new file mode 100644 index 000000000..70e7449f6 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: ba9b37979cbbf4e3987f40254af0b291 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs new file mode 100644 index 000000000..e5d75e99a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs @@ -0,0 +1,9 @@ +using Mirror; + +namespace WeaverSyncSetTests.SyncSet +{ + class SyncSet : NetworkBehaviour + { + public readonly SyncList Foo; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs.meta new file mode 100644 index 000000000..d146af972 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSet.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8f81e2e962dfc4eaa8650c8bb1c32512 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs new file mode 100644 index 000000000..ac78ad2e9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs @@ -0,0 +1,11 @@ +using Mirror; + +namespace WeaverSyncSetTests.SyncSetByteValid +{ + class SyncSetByteValid : NetworkBehaviour + { + class MyByteClass : SyncHashSet { }; + + readonly MyByteClass Foo; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs.meta new file mode 100644 index 000000000..8b58d0c57 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetByteValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f4eb6f81ca19c4af69e957887a8cd83d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs new file mode 100644 index 000000000..fc895897d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverSyncSetTests.SyncSetGenericAbstractInheritance +{ + class SyncSetGenericAbstractInheritance : NetworkBehaviour + { + readonly SomeSetInt superSyncSetString = new SomeSetInt(); + + public abstract class SomeSet : SyncHashSet { } + + public class SomeSetInt : SomeSet { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs.meta new file mode 100644 index 000000000..62885aba8 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericAbstractInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2aa229fc500d04d2884bcef3b1aaa81a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs new file mode 100644 index 000000000..9ecdbfd79 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverSyncSetTests.SyncSetGenericInheritance +{ + class SyncSetGenericInheritance : NetworkBehaviour + { + readonly SomeSetInt someSet = new SomeSetInt(); + + public class SomeSet : SyncHashSet { } + + public class SomeSetInt : SomeSet { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs.meta new file mode 100644 index 000000000..8e107b9d4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetGenericInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 03b44aff9b61b44768bcd3871e43f479 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs new file mode 100644 index 000000000..6ce3855c0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs @@ -0,0 +1,13 @@ +using Mirror; + +namespace WeaverSyncSetTests.SyncSetInheritance +{ + class SyncSetInheritance : NetworkBehaviour + { + readonly SuperSet superSet = new SuperSet(); + + public class SomeSet : SyncHashSet { } + + public class SuperSet : SomeSet { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs.meta new file mode 100644 index 000000000..d16e42611 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetInheritance.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b0c1252122cf5434589e95b6eb4e4b44 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs new file mode 100644 index 000000000..086d67c49 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs @@ -0,0 +1,17 @@ +using Mirror; + +namespace WeaverSyncSetTests.SyncSetStruct +{ + class SyncSetStruct : NetworkBehaviour + { + readonly MyStructSet Foo; + + struct MyStruct + { + public int potato; + public float floatingpotato; + public double givemetwopotatoes; + } + class MyStructSet : SyncHashSet { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs.meta new file mode 100644 index 000000000..b81f8e85c --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncSetTests_IsSuccess/SyncSetStruct.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91c50a30665544ce9863a61c334c116c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs new file mode 100644 index 000000000..95f3c8a74 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs @@ -0,0 +1,40 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverSyncVarAttributeHookTests : WeaverTestsBuildFromTestName + { + static string OldNewMethodFormat(string hookName, string ValueType) + { + return string.Format("void {0}({1} oldValue, {1} newValue)", hookName, ValueType); + } + + [Test] + public void ErrorWhenNoHookFound() + { + HasError($"Could not find hook for 'health', hook name 'onChangeHealth'. Method signature should be {OldNewMethodFormat("onChangeHealth", "System.Int32")}", + "System.Int32 WeaverSyncVarHookTests.ErrorWhenNoHookFound.ErrorWhenNoHookFound::health"); + } + + [Test] + public void ErrorWhenNoHookWithCorrectParametersFound() + { + HasError($"Could not find hook for 'health', hook name 'onChangeHealth'. Method signature should be {OldNewMethodFormat("onChangeHealth", "System.Int32")}", + "System.Int32 WeaverSyncVarHookTests.ErrorWhenNoHookWithCorrectParametersFound.ErrorWhenNoHookWithCorrectParametersFound::health"); + } + + [Test] + public void ErrorForWrongTypeOldParameter() + { + HasError($"Wrong type for Parameter in hook for 'health', hook name 'onChangeHealth'. Method signature should be {OldNewMethodFormat("onChangeHealth", "System.Int32")}", + "System.Int32 WeaverSyncVarHookTests.ErrorForWrongTypeOldParameter.ErrorForWrongTypeOldParameter::health"); + } + + [Test] + public void ErrorForWrongTypeNewParameter() + { + HasError($"Wrong type for Parameter in hook for 'health', hook name 'onChangeHealth'. Method signature should be {OldNewMethodFormat("onChangeHealth", "System.Int32")}", + "System.Int32 WeaverSyncVarHookTests.ErrorForWrongTypeNewParameter.ErrorForWrongTypeNewParameter::health"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs.meta new file mode 100644 index 000000000..8b4b77a25 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 8b8a829462efd04479519a188e458616 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess.meta new file mode 100644 index 000000000..d4bd9a646 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: e70e01ab41584cecb0bb277ebcec26b6 +timeCreated: 1643083684 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs new file mode 100644 index 000000000..c15648e78 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs @@ -0,0 +1,16 @@ +using Mirror; +using UnityEngine; + +namespace WeaverSyncVarHookTests.FindsHookWithGameObjects +{ + class FindsHookWithGameObjects : NetworkBehaviour + { + [SyncVar(hook = nameof(onTargetChanged))] + GameObject target; + + void onTargetChanged(GameObject oldValue, GameObject newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs.meta new file mode 100644 index 000000000..48caf7359 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithGameObjects.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 91ba1f1636e994d35926247a1b5ddfeb +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs new file mode 100644 index 000000000..b9f5faa48 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs @@ -0,0 +1,16 @@ +using Mirror; + + +namespace WeaverSyncVarHookTests.FindsHookWithNetworkIdentity +{ + class FindsHookWithNetworkIdentity : NetworkBehaviour + { + [SyncVar(hook = nameof(onTargetChanged))] + NetworkIdentity target; + + void onTargetChanged(NetworkIdentity oldValue, NetworkIdentity newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs.meta new file mode 100644 index 000000000..c05f649e4 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithNetworkIdentity.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fa0f9d77be7ab465d8eaecf94109e28c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs new file mode 100644 index 000000000..adb9a3c2d --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs @@ -0,0 +1,21 @@ +using Mirror; +using UnityEngine; + +namespace WeaverSyncVarHookTests.FindsHookWithOtherOverloadsInOrder +{ + class FindsHookWithOtherOverloadsInOrder : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + void onChangeHealth(int oldValue, int newValue) + { + + } + + void onChangeHealth(Vector3 anotherValue, bool secondArg) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs.meta new file mode 100644 index 000000000..5d2db7f05 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInOrder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4ca8d2a8003b643deb817d22a5c18be9 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs new file mode 100644 index 000000000..6b9aa1e30 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs @@ -0,0 +1,21 @@ +using Mirror; +using UnityEngine; + +namespace WeaverSyncVarHookTests.FindsHookWithOtherOverloadsInReverseOrder +{ + class FindsHookWithOtherOverloadsInReverseOrder : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + void onChangeHealth(Vector3 anotherValue, bool secondArg) + { + + } + + void onChangeHealth(int oldValue, int newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs.meta new file mode 100644 index 000000000..cfe2d8767 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsHookWithOtherOverloadsInReverseOrder.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4aa8df7feb1364a86a5088146558de8c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs new file mode 100644 index 000000000..c523785ed --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.FindsPrivateHook +{ + class FindsPrivateHook : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + void onChangeHealth(int oldValue, int newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs.meta new file mode 100644 index 000000000..d752182c7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPrivateHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: d4635f4b5c7734921bb185fb09daef52 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs new file mode 100644 index 000000000..8252d2282 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.FindsPublicHook +{ + class FindsPublicHook : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + public void onChangeHealth(int oldValue, int newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs.meta new file mode 100644 index 000000000..1cb18561e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsPublicHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: fd5da6f7c90d9446787ab8f1c0bbb499 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs new file mode 100644 index 000000000..dae2c8245 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.FindsStaticHook +{ + class FindsStaticHook : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + static void onChangeHealth(int oldValue, int newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs.meta new file mode 100644 index 000000000..625aeecdd --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests_IsSuccess/FindsStaticHook.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 5099d9a55253341919aeff0513a096de +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeNewParameter.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeNewParameter.cs new file mode 100644 index 000000000..bd2b6db7a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeNewParameter.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.ErrorForWrongTypeNewParameter +{ + class ErrorForWrongTypeNewParameter : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + void onChangeHealth(int oldValue, float wrongNewValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeOldParameter.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeOldParameter.cs new file mode 100644 index 000000000..3e75887a0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorForWrongTypeOldParameter.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.ErrorForWrongTypeOldParameter +{ + class ErrorForWrongTypeOldParameter : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + void onChangeHealth(float wrongOldValue, int newValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookFound.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookFound.cs new file mode 100644 index 000000000..0e78f7213 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookFound.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.ErrorWhenNoHookFound +{ + class ErrorWhenNoHookFound : NetworkBehaviour + { + [SyncVar(hook = "onChangeHealth")] + int health; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookWithCorrectParametersFound.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookWithCorrectParametersFound.cs new file mode 100644 index 000000000..74592f053 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeHookTests~/ErrorWhenNoHookWithCorrectParametersFound.cs @@ -0,0 +1,20 @@ +using Mirror; + +namespace WeaverSyncVarHookTests.ErrorWhenNoHookWithCorrectParametersFound +{ + class ErrorWhenNoHookWithCorrectParametersFound : NetworkBehaviour + { + [SyncVar(hook = nameof(onChangeHealth))] + int health; + + void onChangeHealth(int someOtherValue) + { + + } + + void onChangeHealth(int someOtherValue, int moreValue, bool anotherValue) + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs new file mode 100644 index 000000000..08fa8c03a --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs @@ -0,0 +1,71 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverSyncVarAttributeTests : WeaverTestsBuildFromTestName + { + [Test] + public void SyncVarsStatic() + { + HasError("invalidVar cannot be static", + "System.Int32 WeaverSyncVarTests.SyncVarsStatic.SyncVarsStatic::invalidVar"); + } + + [Test] + public void SyncVarsGenericParam() + { + HasError("Cannot generate writer for generic type MySyncVar`1. Use a supported type or provide a custom writer", + "WeaverSyncVarTests.SyncVarsGenericParam.SyncVarsGenericParam/MySyncVar`1"); + HasError("invalidVar has unsupported type. Use a supported Mirror type instead", + "WeaverSyncVarTests.SyncVarsGenericParam.SyncVarsGenericParam/MySyncVar`1 WeaverSyncVarTests.SyncVarsGenericParam.SyncVarsGenericParam::invalidVar"); + } + + [Test] + public void SyncVarsInterface() + { + HasError("Cannot generate writer for interface IMySyncVar. Use a supported type or provide a custom writer", + "WeaverSyncVarTests.SyncVarsInterface.SyncVarsInterface/IMySyncVar"); + HasError("invalidVar has unsupported type. Use a supported Mirror type instead", + "WeaverSyncVarTests.SyncVarsInterface.SyncVarsInterface/IMySyncVar WeaverSyncVarTests.SyncVarsInterface.SyncVarsInterface::invalidVar"); + } + + [Test] + public void SyncVarsUnityComponent() + { + HasError("Cannot generate writer for component type TextMesh. Use a supported type or provide a custom writer", + "UnityEngine.TextMesh"); + HasError("invalidVar has unsupported type. Use a supported Mirror type instead", + "UnityEngine.TextMesh WeaverSyncVarTests.SyncVarsUnityComponent.SyncVarsUnityComponent::invalidVar"); + } + + [Test] + public void SyncVarsCantBeArray() + { + HasError("thisShouldntWork has invalid type. Use SyncLists instead of arrays", + "System.Int32[] WeaverSyncVarTests.SyncVarsCantBeArray.SyncVarsCantBeArray::thisShouldntWork"); + } + + [Test] + public void SyncVarsSyncList() + { + // NOTE if this test fails without a warning: + // that happens if after WeaverAssembler->AssemblyBuilder.Build(), + // Unity invokes ILPostProcessor internally. + // and we invoke it from WeaverAssembler buildFinished again. + // => make sure that our ILPostProcessor does nto run on + // WeaverAssembler assemblies + HasNoErrors(); + HasWarning("syncobj has [SyncVar] attribute. SyncLists should not be marked with SyncVar", + "WeaverSyncVarTests.SyncVarsSyncList.SyncVarsSyncList/SyncObjImplementer WeaverSyncVarTests.SyncVarsSyncList.SyncVarsSyncList::syncobj"); + HasWarning("syncints has [SyncVar] attribute. SyncLists should not be marked with SyncVar", + "Mirror.SyncList`1 WeaverSyncVarTests.SyncVarsSyncList.SyncVarsSyncList::syncints"); + } + + [Test] + public void SyncVarsMoreThanMax() + { + HasError("SyncVarsMoreThanMax has > 64 SyncVars. Consider refactoring your class into multiple components", + "WeaverSyncVarTests.SyncVarsMoreThanMax.SyncVarsMoreThanMax"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs.meta new file mode 100644 index 000000000..770100cfe --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c619cb5c467af2a4dafb611d50b84cbf +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess.meta new file mode 100644 index 000000000..f7a809d01 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 9aa873725d92477cbdf19f333bb5ca96 +timeCreated: 1643083781 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs new file mode 100644 index 000000000..fd97fca85 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsDerivedNetworkBehaviour +{ + class MyBehaviour : NetworkBehaviour + { + public int abc = 123; + } + class SyncVarsDerivedNetworkBehaviour : NetworkBehaviour + { + [SyncVar] + MyBehaviour invalidVar; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs.meta new file mode 100644 index 000000000..fffb7a402 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsDerivedNetworkBehaviour.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: f453a8bb28a4a4deb9417d2a29c20aef +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs new file mode 100644 index 000000000..97f4c4de0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs @@ -0,0 +1,74 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsExactlyMax +{ + // SyncVar dirty mask is 64 bit. exactly 64 should work. + class SyncVarsExactlyMax : NetworkBehaviour + { + // 1..64 + [SyncVar] int var1; + [SyncVar] int var2; + [SyncVar] int var3; + [SyncVar] int var4; + [SyncVar] int var5; + [SyncVar] int var6; + [SyncVar] int var7; + [SyncVar] int var8; + [SyncVar] int var9; + [SyncVar] int var10; + [SyncVar] int var11; + [SyncVar] int var12; + [SyncVar] int var13; + [SyncVar] int var14; + [SyncVar] int var15; + [SyncVar] int var16; + [SyncVar] int var17; + [SyncVar] int var18; + [SyncVar] int var19; + [SyncVar] int var20; + [SyncVar] int var21; + [SyncVar] int var22; + [SyncVar] int var23; + [SyncVar] int var24; + [SyncVar] int var25; + [SyncVar] int var26; + [SyncVar] int var27; + [SyncVar] int var28; + [SyncVar] int var29; + [SyncVar] int var30; + [SyncVar] int var31; + [SyncVar] int var32; + [SyncVar] int var33; + [SyncVar] int var34; + [SyncVar] int var35; + [SyncVar] int var36; + [SyncVar] int var37; + [SyncVar] int var38; + [SyncVar] int var39; + [SyncVar] int var40; + [SyncVar] int var41; + [SyncVar] int var42; + [SyncVar] int var43; + [SyncVar] int var44; + [SyncVar] int var45; + [SyncVar] int var46; + [SyncVar] int var47; + [SyncVar] int var48; + [SyncVar] int var49; + [SyncVar] int var50; + [SyncVar] int var51; + [SyncVar] int var52; + [SyncVar] int var53; + [SyncVar] int var54; + [SyncVar] int var55; + [SyncVar] int var56; + [SyncVar] int var57; + [SyncVar] int var58; + [SyncVar] int var59; + [SyncVar] int var60; + [SyncVar] int var61; + [SyncVar] int var62; + [SyncVar] int var63; + [SyncVar] int var64; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs.meta new file mode 100644 index 000000000..bda0ddfc0 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsExactlyMax.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: ecee7f6e569f44afbaa0b6c1cbf2d16c +timeCreated: 1631799739 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs new file mode 100644 index 000000000..710181819 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs @@ -0,0 +1,86 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsValid +{ + class SyncVarsValid : NetworkBehaviour + { + [SyncVar(hook = nameof(OnChangeHealth))] + int health; + + [SyncVar] int var2; + [SyncVar] int var3; + [SyncVar] int var4; + [SyncVar] int var5; + [SyncVar] int var6; + [SyncVar] int var7; + [SyncVar] int var8; + [SyncVar] int var9; + [SyncVar] int var10; + [SyncVar] int var11; + [SyncVar] int var12; + [SyncVar] int var13; + [SyncVar] int var14; + [SyncVar] int var15; + [SyncVar] int var16; + [SyncVar] int var17; + [SyncVar] int var18; + [SyncVar] int var19; + [SyncVar] int var20; + [SyncVar] int var21; + [SyncVar] int var22; + [SyncVar] int var23; + [SyncVar] int var24; + [SyncVar] int var25; + [SyncVar] int var26; + [SyncVar] int var27; + [SyncVar] int var28; + [SyncVar] int var29; + [SyncVar] int var30; + [SyncVar] int var31; + [SyncVar] int var32; + [SyncVar] int var33; + [SyncVar] int var34; + [SyncVar] int var35; + [SyncVar] int var36; + [SyncVar] int var37; + [SyncVar] int var38; + [SyncVar] int var39; + [SyncVar] int var40; + [SyncVar] int var41; + [SyncVar] int var42; + [SyncVar] int var43; + [SyncVar] int var44; + [SyncVar] int var45; + [SyncVar] int var46; + [SyncVar] int var47; + [SyncVar] int var48; + [SyncVar] int var49; + [SyncVar] int var50; + [SyncVar] int var51; + [SyncVar] int var52; + [SyncVar] int var53; + [SyncVar] int var54; + [SyncVar] int var55; + [SyncVar] int var56; + [SyncVar] int var57; + [SyncVar] int var58; + [SyncVar] int var59; + [SyncVar] int var60; + [SyncVar] int var61; + [SyncVar] int var62; + [SyncVar] int var63; + + public void TakeDamage(int amount) + { + if (!isServer) + return; + + health -= amount; + } + + void OnChangeHealth(int oldHealth, int newHealth) + { + // do things with your health bar + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs.meta new file mode 100644 index 000000000..08d50f232 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests_IsSuccess/SyncVarsValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 05594340f435e4de28388efc46a33ae1 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsCantBeArray.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsCantBeArray.cs new file mode 100644 index 000000000..7f346da6f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsCantBeArray.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsCantBeArray +{ + class SyncVarsCantBeArray : NetworkBehaviour + { + [SyncVar] + int[] thisShouldntWork = new int[100]; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsGenericParam.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsGenericParam.cs new file mode 100644 index 000000000..b965e711b --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsGenericParam.cs @@ -0,0 +1,15 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsGenericParam +{ + class SyncVarsGenericParam : NetworkBehaviour + { + struct MySyncVar + { + T abc; + } + + [SyncVar] + MySyncVar invalidVar = new MySyncVar(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsInterface.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsInterface.cs new file mode 100644 index 000000000..c32cb3092 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsInterface.cs @@ -0,0 +1,14 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsInterface +{ + class SyncVarsInterface : NetworkBehaviour + { + interface IMySyncVar + { + void interfaceMethod(); + } + [SyncVar] + IMySyncVar invalidVar; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsMoreThanMax.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsMoreThanMax.cs new file mode 100644 index 000000000..7f9fde206 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsMoreThanMax.cs @@ -0,0 +1,74 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsMoreThanMax +{ + // SyncVar dirty mask is 64 bit. more than 64 should show an error. + class SyncVarsMoreThanMax : NetworkBehaviour + { + [SyncVar] int var1; + [SyncVar] int var2; + [SyncVar] int var3; + [SyncVar] int var4; + [SyncVar] int var5; + [SyncVar] int var6; + [SyncVar] int var7; + [SyncVar] int var8; + [SyncVar] int var9; + [SyncVar] int var10; + [SyncVar] int var11; + [SyncVar] int var12; + [SyncVar] int var13; + [SyncVar] int var14; + [SyncVar] int var15; + [SyncVar] int var16; + [SyncVar] int var17; + [SyncVar] int var18; + [SyncVar] int var19; + [SyncVar] int var20; + [SyncVar] int var21; + [SyncVar] int var22; + [SyncVar] int var23; + [SyncVar] int var24; + [SyncVar] int var25; + [SyncVar] int var26; + [SyncVar] int var27; + [SyncVar] int var28; + [SyncVar] int var29; + [SyncVar] int var30; + [SyncVar] int var31; + [SyncVar] int var32; + [SyncVar] int var33; + [SyncVar] int var34; + [SyncVar] int var35; + [SyncVar] int var36; + [SyncVar] int var37; + [SyncVar] int var38; + [SyncVar] int var39; + [SyncVar] int var40; + [SyncVar] int var41; + [SyncVar] int var42; + [SyncVar] int var43; + [SyncVar] int var44; + [SyncVar] int var45; + [SyncVar] int var46; + [SyncVar] int var47; + [SyncVar] int var48; + [SyncVar] int var49; + [SyncVar] int var50; + [SyncVar] int var51; + [SyncVar] int var52; + [SyncVar] int var53; + [SyncVar] int var54; + [SyncVar] int var55; + [SyncVar] int var56; + [SyncVar] int var57; + [SyncVar] int var58; + [SyncVar] int var59; + [SyncVar] int var60; + [SyncVar] int var61; + [SyncVar] int var62; + [SyncVar] int var63; + [SyncVar] int var64; + [SyncVar] int var65; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsStatic.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsStatic.cs new file mode 100644 index 000000000..a89a71f32 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsStatic.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverSyncVarTests.SyncVarsStatic +{ + class SyncVarsStatic : NetworkBehaviour + { + [SyncVar] + static int invalidVar = 123; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsSyncList.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsSyncList.cs new file mode 100644 index 000000000..9851fc4ce --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsSyncList.cs @@ -0,0 +1,25 @@ +using Mirror; +using System; + +namespace WeaverSyncVarTests.SyncVarsSyncList +{ + + class SyncVarsSyncList : NetworkBehaviour + { + public class SyncObjImplementer : SyncObject + { + public override void ClearChanges() { } + public override void OnSerializeAll(NetworkWriter writer) { } + public override void OnSerializeDelta(NetworkWriter writer) { } + public override void OnDeserializeAll(NetworkReader reader) { } + public override void OnDeserializeDelta(NetworkReader reader) { } + public override void Reset() { } + } + + [SyncVar] + SyncObjImplementer syncobj; + + [SyncVar] + SyncList syncints; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsUnityComponent.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsUnityComponent.cs new file mode 100644 index 000000000..05dfcacbb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverSyncVarAttributeTests~/SyncVarsUnityComponent.cs @@ -0,0 +1,11 @@ +using Mirror; +using UnityEngine; + +namespace WeaverSyncVarTests.SyncVarsUnityComponent +{ + class SyncVarsUnityComponent : NetworkBehaviour + { + [SyncVar] + TextMesh invalidVar; + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs new file mode 100644 index 000000000..b2f575bb7 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs @@ -0,0 +1,35 @@ +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public class WeaverTargetRpcTests : WeaverTestsBuildFromTestName + { + [Test] + public void ErrorWhenTargetRpcIsStatic() + { + HasError("TargetCantBeStatic must not be static", + "System.Void WeaverTargetRpcTests.ErrorWhenTargetRpcIsStatic.ErrorWhenTargetRpcIsStatic::TargetCantBeStatic(Mirror.NetworkConnection)"); + } + + [Test] + public void ErrorWhenNetworkConnectionIsNotTheFirstParameter() + { + HasError("TargetRpcMethod has invalid parameter nc. Cannot pass NetworkConnections", + "System.Void WeaverTargetRpcTests.ErrorWhenNetworkConnectionIsNotTheFirstParameter.ErrorWhenNetworkConnectionIsNotTheFirstParameter::TargetRpcMethod(System.Int32,Mirror.NetworkConnection)"); + } + + [Test] + public void AbstractTargetRpc() + { + HasError("Abstract TargetRpc are currently not supported, use virtual method instead", + "System.Void WeaverTargetRpcTests.AbstractTargetRpc.AbstractTargetRpc::TargetDoSomething()"); + } + + [Test] + public void OverrideAbstractTargetRpc() + { + HasError("Abstract TargetRpc are currently not supported, use virtual method instead", + "System.Void WeaverTargetRpcTests.OverrideAbstractTargetRpc.BaseBehaviour::TargetDoSomething()"); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs.meta new file mode 100644 index 000000000..e31608660 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 9e7275e59ed5b274eac8299115b54a56 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess.meta new file mode 100644 index 000000000..73c2747e1 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 61b1dec97fbc4c9cae92609b5bbd9322 +timeCreated: 1643083911 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs new file mode 100644 index 000000000..546b91ddc --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs @@ -0,0 +1,23 @@ +using Mirror; + + +namespace WeaverTargetRpcTests.OverrideVirtualTargetRpc +{ + class OverrideVirtualTargetRpc : baseBehaviour + { + [TargetRpc] + protected override void TargetDoSomething() + { + // do something + } + } + + class baseBehaviour : NetworkBehaviour + { + [TargetRpc] + protected virtual void TargetDoSomething() + { + + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs.meta new file mode 100644 index 000000000..ea7d34217 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/OverrideVirtualTargetRpc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: cd443394428a741c19d59c118cc8ab6c +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs new file mode 100644 index 000000000..ca7b08ae3 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverTargetRpcTests.TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection +{ + class TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection : NetworkBehaviour + { + [TargetRpc] + void TargetRpcMethod(int usefulNumber) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs.meta new file mode 100644 index 000000000..5b045fce9 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanHaveOtherParametersWhileSkipingNetworkConnection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 852a7a1887709494ba0b3719389da4d5 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs new file mode 100644 index 000000000..ec0858925 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverTargetRpcTests.TargetRpcCanSkipNetworkConnection +{ + class TargetRpcCanSkipNetworkConnection : NetworkBehaviour + { + [TargetRpc] + void TargetRpcMethod() { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs.meta new file mode 100644 index 000000000..3428d838e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcCanSkipNetworkConnection.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: b5612ba5b8ef645568b6b79d5548518f +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs new file mode 100644 index 000000000..38e046054 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverTargetRpcTests.TargetRpcValid +{ + class TargetRpcValid : NetworkBehaviour + { + [TargetRpc] + void TargetThatIsTotallyValid(NetworkConnection nc) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs.meta new file mode 100644 index 000000000..1c46edade --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/TargetRpcValid.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 2ec5e487c9cb7465b80ebdc3cfed7b2d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs new file mode 100644 index 000000000..dd00afd41 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs @@ -0,0 +1,14 @@ +using Mirror; + + +namespace WeaverTargetRpcTests.VirtualTargetRpc +{ + class VirtualTargetRpc : NetworkBehaviour + { + [TargetRpc] + protected virtual void TargetDoSomething() + { + // do something + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs.meta new file mode 100644 index 000000000..72d39d085 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests_IsSuccess/VirtualTargetRpc.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7b717a43817274f04b5a0fcf90418c59 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/AbstractTargetRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/AbstractTargetRpc.cs new file mode 100644 index 000000000..4cc89278e --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/AbstractTargetRpc.cs @@ -0,0 +1,11 @@ +using Mirror; + + +namespace WeaverTargetRpcTests.AbstractTargetRpc +{ + abstract class AbstractTargetRpc : NetworkBehaviour + { + [TargetRpc] + protected abstract void TargetDoSomething(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenNetworkConnectionIsNotTheFirstParameter.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenNetworkConnectionIsNotTheFirstParameter.cs new file mode 100644 index 000000000..60201be5f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenNetworkConnectionIsNotTheFirstParameter.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverTargetRpcTests.ErrorWhenNetworkConnectionIsNotTheFirstParameter +{ + class ErrorWhenNetworkConnectionIsNotTheFirstParameter : NetworkBehaviour + { + [TargetRpc] + void TargetRpcMethod(int potatoesRcool, NetworkConnection nc) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenTargetRpcIsStatic.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenTargetRpcIsStatic.cs new file mode 100644 index 000000000..3cd686759 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/ErrorWhenTargetRpcIsStatic.cs @@ -0,0 +1,10 @@ +using Mirror; + +namespace WeaverTargetRpcTests.ErrorWhenTargetRpcIsStatic +{ + class ErrorWhenTargetRpcIsStatic : NetworkBehaviour + { + [TargetRpc] + static void TargetCantBeStatic(NetworkConnection nc) { } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/OverrideAbstractTargetRpc.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/OverrideAbstractTargetRpc.cs new file mode 100644 index 000000000..63b7fe794 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTargetRpcTests~/OverrideAbstractTargetRpc.cs @@ -0,0 +1,20 @@ +using Mirror; + + +namespace WeaverTargetRpcTests.OverrideAbstractTargetRpc +{ + class OverrideAbstractTargetRpc : BaseBehaviour + { + [TargetRpc] + protected override void TargetDoSomething() + { + // do something + } + } + + abstract class BaseBehaviour : NetworkBehaviour + { + [TargetRpc] + protected abstract void TargetDoSomething(); + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs new file mode 100644 index 000000000..8259249cb --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs @@ -0,0 +1,77 @@ +using System.Collections.Generic; +using System.IO; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Weaver.Tests +{ + [TestFixture] + [Category("Weaver")] + public abstract class WeaverTests + { + protected List weaverErrors = new List(); + void HandleWeaverError(string msg) + { + LogAssert.ignoreFailingMessages = true; + Debug.LogError(msg); + LogAssert.ignoreFailingMessages = false; + + weaverErrors.Add(msg); + } + + protected List weaverWarnings = new List(); + void HandleWeaverWarning(string msg) + { + Debug.LogWarning(msg); + weaverWarnings.Add(msg); + } + + protected void BuildAndWeaveTestAssembly(string className, string testName) + { + string testSourceDirectory = className + "~"; + WeaverAssembler.OutputFile = Path.Combine(testSourceDirectory, testName + ".dll"); + WeaverAssembler.AddSourceFiles(new string[] { Path.Combine(testSourceDirectory, testName + ".cs") }); + WeaverAssembler.Build(HandleWeaverWarning, HandleWeaverError); + + Assert.That(WeaverAssembler.CompilerErrors, Is.False); + foreach (string error in weaverErrors) + { + // ensure all errors have a location + Assert.That(error, Does.Match(@"\(at .*\)$")); + } + } + + [OneTimeSetUp] + public void FixtureSetup() + { +#if !UNITY_2020_3_OR_NEWER + // CompilationFinishedHook is used for tests pre-2020 ILPostProcessor + CompilationFinishedHook.UnityLogEnabled = false; + CompilationFinishedHook.OnWeaverError += HandleWeaverError; + CompilationFinishedHook.OnWeaverWarning += HandleWeaverWarning; +#endif + } + + [OneTimeTearDown] + public void FixtureCleanup() + { +#if !UNITY_2020_3_OR_NEWER + // CompilationFinishedHook is used for tests pre-2020 ILPostProcessor + CompilationFinishedHook.OnWeaverError -= HandleWeaverError; + CompilationFinishedHook.OnWeaverWarning -= HandleWeaverWarning; + CompilationFinishedHook.UnityLogEnabled = true; +#endif + } + + [TearDown] + public void TestCleanup() + { + WeaverAssembler.DeleteOutputOnClear = true; + WeaverAssembler.Clear(); + + weaverWarnings.Clear(); + weaverErrors.Clear(); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs.meta new file mode 100644 index 000000000..a98d9222f --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c3f52dab9c479dd4586d0aceeb2390f0 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs b/Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs new file mode 100644 index 000000000..8341f5c56 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs @@ -0,0 +1,40 @@ +using System.Linq; +using NUnit.Framework; + +namespace Mirror.Weaver.Tests +{ + public abstract class WeaverTestsBuildFromTestName : WeaverTests + { + [SetUp] + public virtual void TestSetup() + { + string className = TestContext.CurrentContext.Test.ClassName.Split('.').Last(); + + BuildAndWeaveTestAssembly(className, TestContext.CurrentContext.Test.Name); + } + + // IMPORTANT: IsSuccess() tests can almost ALL be moved into regular + // C#/folders without running AssemblyBuilder on them. + // See README.md int his folder. + protected void IsSuccess() + { + Assert.That(weaverErrors, Is.Empty); + Assert.That(weaverWarnings, Is.Empty); + } + + protected void HasNoErrors() + { + Assert.That(weaverErrors, Is.Empty); + } + + protected void HasError(string messsage, string atType) + { + Assert.That(weaverErrors, Contains.Item($"{messsage} (at {atType})")); + } + + protected void HasWarning(string messsage, string atType) + { + Assert.That(weaverWarnings, Contains.Item($"{messsage} (at {atType})")); + } + } +} diff --git a/Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs.meta b/Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs.meta new file mode 100644 index 000000000..bbaf34125 --- /dev/null +++ b/Assets/Mirror/Tests/Editor/Weaver/WeaverTestsBuildFromTestName.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: aa404c2d6a88488f9b6e42a87b01cd61 +timeCreated: 1629525351 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Runtime.meta b/Assets/Mirror/Tests/Runtime.meta new file mode 100644 index 000000000..41c7a7c0b --- /dev/null +++ b/Assets/Mirror/Tests/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 925dd1ba8ae9d4a969a57221e025309c +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs b/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs new file mode 100644 index 000000000..22197d033 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs @@ -0,0 +1,174 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text.RegularExpressions; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime.ClientSceneTests +{ + public class TestListenerBehaviour : NetworkBehaviour + { + // If object is destroyed then both OnDisable and OnDestroy will be called + public event Action onDestroyCalled; + public event Action onDisableCalled; + + public void OnDisable() => onDisableCalled?.Invoke(); + void OnDestroy() => onDestroyCalled?.Invoke(); + } + + // A network Behaviour that changes NetworkIdentity.spawned in OnDisable + public class BadBehaviour : NetworkBehaviour + { + public void OnDisable() + { + GameObject go = new GameObject(); + NetworkIdentity netId = go.AddComponent(); + const int id = 32032; + netId.netId = id; + + NetworkClient.spawned.Add(id, netId); + } + } + + public class ClientSceneTests_DestroyAllClientObjects : MirrorPlayModeTest + { + Dictionary unspawnHandlers => NetworkClient.unspawnHandlers; + + [UnitySetUp] + public override IEnumerator UnitySetUp() + { + yield return base.UnitySetUp(); + + // start server & client and wait 1 frame + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + yield return null; + } + + [UnityTearDown] + public override IEnumerator UnityTearDown() + { + unspawnHandlers.Clear(); + base.TearDown(); + yield return null; + } + + TestListenerBehaviour CreateAndAddObject(ulong sceneId) + { + CreateNetworkedAndSpawn(out GameObject go, out NetworkIdentity identity, out TestListenerBehaviour listener); + identity.sceneId = sceneId; + return listener; + } + + [UnityTest] + public IEnumerator DestroysAllNetworkPrefabsInScene() + { + // sceneId 0 is prefab + TestListenerBehaviour listener1 = CreateAndAddObject(0); + TestListenerBehaviour listener2 = CreateAndAddObject(0); + + int destroyCalled1 = 0; + int destroyCalled2 = 0; + + listener1.onDestroyCalled += () => destroyCalled1++; + listener2.onDestroyCalled += () => destroyCalled2++; + + NetworkClient.DestroyAllClientObjects(); + + // wait for frame to make sure unity events are called + yield return null; + + Assert.That(destroyCalled1, Is.EqualTo(1)); + Assert.That(destroyCalled2, Is.EqualTo(1)); + } + + [UnityTest] + public IEnumerator DisablesAllNetworkSceneObjectsInScene() + { + // sceneId 0 is prefab + TestListenerBehaviour listener1 = CreateAndAddObject(101); + TestListenerBehaviour listener2 = CreateAndAddObject(102); + + int disableCalled1 = 0; + int disableCalled2 = 0; + + listener1.onDisableCalled += () => disableCalled1++; + listener2.onDisableCalled += () => disableCalled2++; + + int destroyCalled1 = 0; + int destroyCalled2 = 0; + + listener1.onDestroyCalled += () => destroyCalled1++; + listener2.onDestroyCalled += () => destroyCalled2++; + + NetworkClient.DestroyAllClientObjects(); + + // wait for frame to make sure unity events are called + yield return null; + + Assert.That(disableCalled1, Is.EqualTo(1)); + Assert.That(disableCalled2, Is.EqualTo(1)); + + Assert.That(destroyCalled1, Is.EqualTo(0), "Scene objects should not be destroyed"); + Assert.That(destroyCalled2, Is.EqualTo(0), "Scene objects should not be destroyed"); + } + + [Test] + public void CallsUnspawnHandlerInsteadOfDestroy() + { + // sceneId 0 is prefab + TestListenerBehaviour listener1 = CreateAndAddObject(0); + TestListenerBehaviour listener2 = CreateAndAddObject(0); + + Guid guid1 = Guid.NewGuid(); + Guid guid2 = Guid.NewGuid(); + + int unspawnCalled1 = 0; + int unspawnCalled2 = 0; + + unspawnHandlers.Add(guid1, x => unspawnCalled1++); + unspawnHandlers.Add(guid2, x => unspawnCalled2++); + listener1.GetComponent().assetId = guid1; + listener2.GetComponent().assetId = guid2; + + int disableCalled1 = 0; + int disableCalled2 = 0; + + listener1.onDisableCalled += () => disableCalled1++; + listener2.onDisableCalled += () => disableCalled2++; + + NetworkClient.DestroyAllClientObjects(); + + Assert.That(unspawnCalled1, Is.EqualTo(1)); + Assert.That(unspawnCalled2, Is.EqualTo(1)); + + Assert.That(disableCalled1, Is.EqualTo(0), "Object with UnspawnHandler should not be destroyed"); + Assert.That(disableCalled2, Is.EqualTo(0), "Object with UnspawnHandler should not be destroyed"); + } + + [Test] + public void ClearsSpawnedList() + { + // sceneId 0 is prefab + TestListenerBehaviour listener1 = CreateAndAddObject(0); + TestListenerBehaviour listener2 = CreateAndAddObject(0); + + NetworkClient.DestroyAllClientObjects(); + + Assert.That(NetworkClient.spawned, Is.Empty); + } + + [Test] + public void CatchesAndLogsExeptionWhenSpawnedListIsChanged() + { + // create spawned (needs to be added to .spawned!) + CreateNetworkedAndSpawn(out GameObject badGameObject, out NetworkIdentity identity, out BadBehaviour bad); + + LogAssert.Expect(LogType.Exception, new Regex("InvalidOperationException")); + LogAssert.Expect(LogType.Error, "Could not DestroyAllClientObjects because spawned list was modified during loop, make sure you are not modifying NetworkIdentity.spawned by calling NetworkServer.Destroy or NetworkServer.Spawn in OnDestroy or OnDisable."); + NetworkClient.DestroyAllClientObjects(); + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs.meta b/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs.meta new file mode 100644 index 000000000..420611c3f --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_DestroyAllClientObjects.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4a5f4e31bf3f4dc4fa194eabc2cf80bd +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs new file mode 100644 index 000000000..91e306f92 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs @@ -0,0 +1,68 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime.ClientSceneTests +{ + public class ClientSceneTests_LocalPlayer : ClientSceneTestsBase + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + + [UnityTest] + public IEnumerator LocalPlayerIsSetToNullAfterDestroy() + { + // need spawned local player + CreateNetworkedAndSpawnPlayer(out GameObject go, out _, NetworkServer.localConnection); + + // need to have localPlayer set for this test + Assert.That(NetworkClient.localPlayer, !Is.Null); + + // destroy, wait one frame, localPlayer should be cleared + GameObject.Destroy(go); + yield return null; + Assert.IsTrue(NetworkClient.localPlayer is null, "local player should be set to c# null"); + } + + [UnityTest] + public IEnumerator DestroyingOtherObjectDoesntEffectLocalPlayer() + { + // need spawned not-local-player + CreateNetworkedAndSpawn(out _, out NetworkIdentity notPlayer); + + // need spawned local player + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity player, NetworkServer.localConnection); + + // need to have localPlayer set for this test + Assert.That(NetworkClient.localPlayer, !Is.Null); + + // destroy, wait one frame, localPlayer should remain + GameObject.Destroy(notPlayer); + yield return null; + Assert.IsTrue(NetworkClient.localPlayer != null, "local player should not be cleared"); + Assert.IsTrue(NetworkClient.localPlayer == player, "local player should still be equal to player"); + } + + [UnityTest] + public IEnumerator LocalPlayerIsSetToNullAfterDestroyMessage() + { + // need spawned local player + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity identity, NetworkServer.localConnection); + + // need to have localPlayer set for this test + Assert.That(NetworkClient.localPlayer, !Is.Null); + + // OnObjectDestroy, wait one frame, localPlayer should be cleared + NetworkClient.OnObjectDestroy(new ObjectDestroyMessage{netId = identity.netId}); + yield return null; + Assert.IsTrue(NetworkClient.localPlayer is null, "local player should be set to c# null"); + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs.meta b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs.meta new file mode 100644 index 000000000..b57c8bd6a --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 7c087df10f51b674f94f84ba30a303f4 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs new file mode 100644 index 000000000..ddc5af61a --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs @@ -0,0 +1,48 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime +{ + public class ClientSceneTest_LocalPlayer_AsHost : MirrorPlayModeTest + { + [UnitySetUp] + public override IEnumerator UnitySetUp() + { + yield return base.UnitySetUp(); + + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + } + + [UnityTest] + public IEnumerator LocalPlayerIsSetToNullAfterNetworkDestroy() + { + // need spawned local player + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity identity, NetworkServer.localConnection); + + // need to have localPlayer set for this test + Assert.That(NetworkClient.localPlayer, !Is.Null); + + // unspawn, wait one frame, localPlayer should be cleared + NetworkServer.Destroy(identity.gameObject); + yield return null; + Assert.IsTrue(NetworkClient.localPlayer is null, "local player should be set to c# null"); + } + + [UnityTest] + public IEnumerator LocalPlayerIsSetToNullAfterNetworkUnspawn() + { + // need spawned local player + CreateNetworkedAndSpawnPlayer(out _, out NetworkIdentity identity, NetworkServer.localConnection); + + // need to have localPlayer set for this test + Assert.That(NetworkClient.localPlayer, !Is.Null); + + // unspawn, wait one frame, localPlayer should be cleared + NetworkServer.UnSpawn(identity.gameObject); + yield return null; + Assert.IsTrue(NetworkClient.localPlayer is null, "local player should be set to c# null"); + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs.meta b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs.meta new file mode 100644 index 000000000..e9837dd30 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_LocalPlayer_AsHost.cs.meta @@ -0,0 +1,3 @@ +fileFormatVersion: 2 +guid: 6baedb85368e4347876f7b8b7c5110cb +timeCreated: 1622030080 \ No newline at end of file diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs b/Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs new file mode 100644 index 000000000..4e50852ca --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs @@ -0,0 +1,72 @@ +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime.ClientSceneTests +{ + public class ClientSceneTests_Runtime_RegisterPrefab : ClientSceneTests_RegisterPrefabBase + { + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void Handler_AddsSpawnHandlerToDictionaryForRuntimeObject(RegisterPrefabOverload overload) + { + // create a scene object + CreateNetworked(out GameObject runtimeObject, out NetworkIdentity networkIdentity); + + Debug.Assert(networkIdentity.sceneId == 0, "SceneId was not set to 0"); + Debug.Assert(runtimeObject.GetComponent().sceneId == 0, "SceneId was not set to 0"); + + //test + CallRegisterPrefab(runtimeObject, overload); + Assert.IsTrue(NetworkClient.spawnHandlers.ContainsKey(anotherGuid)); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate)] + public void ErrorForEmptyGuid(RegisterPrefabOverload overload) + { + // create a scene object + CreateNetworked(out GameObject runtimeObject, out _); + + //test + string msg = OverloadWithHandler(overload) + ? $"Can not Register handler for '{runtimeObject.name}' because it had empty assetid. If this is a scene Object use RegisterSpawnHandler instead" + : $"Can not Register '{runtimeObject.name}' because it had empty assetid. If this is a scene Object use RegisterSpawnHandler instead"; + + LogAssert.Expect(LogType.Error, msg); + CallRegisterPrefab(runtimeObject, overload); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_NewAssetId)] + public void PrefabNewGuid_AddsRuntimeObjectToDictionary(RegisterPrefabOverload overload) + { + // create a scene object + CreateNetworked(out GameObject runtimeObject, out NetworkIdentity networkIdentity); + + //test + CallRegisterPrefab(runtimeObject, overload); + + Assert.IsTrue(NetworkClient.prefabs.ContainsKey(anotherGuid)); + Assert.AreEqual(NetworkClient.prefabs[anotherGuid], runtimeObject); + + Assert.AreEqual(networkIdentity.assetId, anotherGuid); + } + + [Test] + [TestCase(RegisterPrefabOverload.Prefab_SpawnDelegate_NewAssetId)] + [TestCase(RegisterPrefabOverload.Prefab_SpawnHandlerDelegate_NewAssetId)] + public void Handler_AddsUnSpawnHandlerToDictionaryForRuntimeObject(RegisterPrefabOverload overload) + { + // create a scene object + CreateNetworked(out GameObject runtimeObject, out _); + + //test + CallRegisterPrefab(runtimeObject, overload); + Assert.IsTrue(NetworkClient.unspawnHandlers.ContainsKey(anotherGuid)); + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs.meta b/Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs.meta new file mode 100644 index 000000000..218c110c4 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/ClientSceneTests_Runtime_RegisterPrefab.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: c47e4b8fb9faba04792f9e4764a9c929 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef b/Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef new file mode 100644 index 000000000..f852e83f7 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef @@ -0,0 +1,22 @@ +{ + "name": "Mirror.Tests.Runtime", + "references": [ + "Mirror", + "Mirror.Components", + "Mirror.Tests.Common" + ], + "optionalUnityReferences": [ + "TestAssemblies" + ], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": true, + "precompiledReferences": [ + "NSubstitute.dll", + "Castle.Core.dll", + "System.Threading.Tasks.Extensions.dll" + ], + "autoReferenced": true, + "defineConstraints": [] +} \ No newline at end of file diff --git a/Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef.meta b/Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef.meta new file mode 100644 index 000000000..fb6f3e343 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Mirror.Tests.Runtime.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b8767e8f08282414ea6026200077ad4c +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs b/Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs new file mode 100644 index 000000000..e92433b73 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs @@ -0,0 +1,79 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime +{ + public class NetworkIdentityTests : MirrorPlayModeTest + { + GameObject gameObject; + NetworkIdentity identity; + + [UnitySetUp] + public override IEnumerator UnitySetUp() + { + yield return base.UnitySetUp(); + CreateNetworked(out gameObject, out identity); + yield return null; + } + + // prevents https://github.com/vis2k/Mirror/issues/1484 + [UnityTest] + public IEnumerator OnDestroyIsServerTrue() + { + // call OnStartServer so that isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // destroy it + // note: we need runtime .Destroy instead of edit mode .DestroyImmediate + // because we can't check isServer after DestroyImmediate + GameObject.Destroy(gameObject); + + // make sure that isServer is still true so we can save players etc. + Assert.That(identity.isServer, Is.True); + + yield return null; + // Confirm it has been destroyed + Assert.That(identity == null, Is.True); + } + + [UnityTest] + public IEnumerator OnDestroyIsServerTrueWhenNetworkServerDestroyIsCalled() + { + // call OnStartServer so that isServer is true + identity.OnStartServer(); + Assert.That(identity.isServer, Is.True); + + // destroy it + NetworkServer.Destroy(gameObject); + + // make sure that isServer is still true so we can save players etc. + Assert.That(identity.isServer, Is.True); + + yield return null; + // Confirm it has been destroyed + Assert.That(identity == null, Is.True); + } + + // imer: There's currently an issue with dropped/skipped serializations + // once a server has been running for around a week, this test should + // highlight the potential cause + [UnityTest] + public IEnumerator TestSerializationWithLargeTimestamps() + { + // 14 * 24 hours per day * 60 minutes per hour * 60 seconds per minute = 14 days + // NOTE: change this to 'float' to see the tests fail + int tick = 14 * 24 * 60 * 60; + NetworkIdentitySerialization serialization = identity.GetSerializationAtTick(tick); + // advance tick + ++tick; + NetworkIdentitySerialization serializationNew = identity.GetSerializationAtTick(tick); + + // if the serialization has been changed the tickTimeStamp should have moved + Assert.That(serialization.tick == serializationNew.tick, Is.False); + yield break; + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs.meta b/Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs.meta new file mode 100644 index 000000000..9f6cfef8d --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/NetworkIdentityTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: ef23592667dfe46948980606ccbe1860 +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs b/Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs new file mode 100644 index 000000000..8186f92d5 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs @@ -0,0 +1,45 @@ +using System.Collections; +using NUnit.Framework; +using UnityEngine.SceneManagement; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime +{ + public class NetworkManagerTests + { + Scene activeScene; + + IEnumerator RunIsActiveSceneTest(string sceneToCheck, bool expected) + { + // wait for first frame to make sure scene is loaded + yield return null; + activeScene = SceneManager.GetActiveScene(); + + bool isActive = NetworkManager.IsSceneActive(sceneToCheck); + Assert.That(isActive, Is.EqualTo(expected)); + } + + [UnityTest] + public IEnumerator IsActiveSceneWorksWithSceneName() + { + yield return RunIsActiveSceneTest(activeScene.name, true); + yield return RunIsActiveSceneTest("NotActiveScene", false); + } + [UnityTest] + public IEnumerator IsActiveSceneWorksWithScenePath() + { + yield return RunIsActiveSceneTest(activeScene.path, true); + yield return RunIsActiveSceneTest("Assets/Mirror/Tests/Runtime/Scenes/NotActiveScene.unity", false); + } + [UnityTest] + public IEnumerator IsActiveSceneIsFalseForScenesWithSameNameButDifferentPath() + { + yield return RunIsActiveSceneTest($"Another/Path/To/{activeScene.path}", false); + } + [UnityTest] + public IEnumerator IsActiveSceneIsFalseForEmptyString() + { + yield return RunIsActiveSceneTest("", false); + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs.meta b/Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs.meta new file mode 100644 index 000000000..592ff091c --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/NetworkManagerTests.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 4cd917fdc9c513d438db90185784d87d +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs new file mode 100644 index 000000000..03559af62 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs @@ -0,0 +1,112 @@ +using System.Collections; +using NUnit.Framework; +using UnityEditor; +using UnityEngine; +using UnityEngine.TestTools; + +namespace Mirror.Tests.Runtime +{ + [TestFixture] + public class NetworkServerRuntimeTest : MirrorPlayModeTest + { + [UnitySetUp] + public override IEnumerator UnitySetUp() + { + yield return base.UnitySetUp(); + + // start server & client and wait 1 frame + NetworkServer.Listen(1); + ConnectHostClientBlockingAuthenticatedAndReady(); + yield return null; + } + + [UnityTest] + public IEnumerator DestroyPlayerForConnectionTest() + { + // create spawned player + CreateNetworkedAndSpawnPlayer(out GameObject player, out _, NetworkServer.localConnection); + + // destroy player for connection, wait 1 frame to unspawn and destroy + NetworkServer.DestroyPlayerForConnection(NetworkServer.localConnection); + yield return null; + + Assert.That(player == null, "Player should be destroyed with DestroyPlayerForConnection"); + } + + [UnityTest] + public IEnumerator RemovePlayerForConnectionTest() + { + // create spawned player + CreateNetworkedAndSpawnPlayer(out GameObject player, out _, NetworkServer.localConnection); + + // remove player for connection, wait 1 frame to unspawn + NetworkServer.RemovePlayerForConnection(NetworkServer.localConnection, false); + yield return null; + + Assert.That(player, Is.Not.Null, "Player should be not be destroyed"); + Assert.That(NetworkServer.localConnection.identity == null, "identity should be null"); + + // respawn player + NetworkServer.AddPlayerForConnection(NetworkServer.localConnection, player); + Assert.That(NetworkServer.localConnection.identity != null, "identity should not be null"); + } + + [UnityTest] + public IEnumerator Shutdown_DestroysAllSpawnedPrefabs() + { + // setup + NetworkServer.Listen(1); + + const string ValidPrefabAssetGuid = "33169286da0313d45ab5bfccc6cf3775"; + GameObject prefab = AssetDatabase.LoadAssetAtPath(AssetDatabase.GUIDToAssetPath(ValidPrefabAssetGuid)); + + NetworkIdentity identity1 = SpawnPrefab(prefab); + NetworkIdentity identity2 = SpawnPrefab(prefab); + + // shutdown, wait 1 frame for unity to destroy objects + NetworkServer.Shutdown(); + yield return null; + + // check that objects were destroyed + // need to use untiy `==` check + Assert.IsTrue(identity1 == null); + Assert.IsTrue(identity2 == null); + + Assert.That(NetworkServer.spawned, Is.Empty); + } + + NetworkIdentity SpawnPrefab(GameObject prefab) + { + GameObject clone1 = GameObject.Instantiate(prefab); + NetworkServer.Spawn(clone1); + NetworkIdentity identity1 = clone1.GetComponent(); + Assert.IsTrue(NetworkServer.spawned.ContainsValue(identity1)); + return identity1; + } + + [Test] + public void Shutdown_DisablesAllSpawnedPrefabs() + { + // setup + NetworkServer.Listen(1); + + // spawn two scene objects + CreateNetworkedAndSpawn(out _, out NetworkIdentity identity1); + CreateNetworkedAndSpawn(out _, out NetworkIdentity identity2); + identity1.sceneId = (ulong)identity1.GetHashCode(); + identity2.sceneId = (ulong)identity2.GetHashCode(); + + // test + NetworkServer.Shutdown(); + + // check that objects were disabled + // need to use untiy `==` check + Assert.IsTrue(identity1 != null); + Assert.IsTrue(identity2 != null); + Assert.IsFalse(identity1.gameObject.activeSelf); + Assert.IsFalse(identity1.gameObject.activeSelf); + + Assert.That(NetworkServer.spawned, Is.Empty); + } + } +} diff --git a/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs.meta b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs.meta new file mode 100644 index 000000000..431c107b0 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/NetworkServerRuntimeTest.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: a94e088c9596a284cb2cb960746ef2ce +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/Scenes.meta b/Assets/Mirror/Tests/Runtime/Scenes.meta new file mode 100644 index 000000000..0e9d6f423 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: 69531c106ae23bc438f789d213c11fce +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity b/Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity new file mode 100644 index 000000000..be02d2d12 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity @@ -0,0 +1,381 @@ +%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.44657898, g: 0.4964133, b: 0.5748178, a: 1} + m_UseRadianceAmbientProbe: 0 +--- !u!157 &3 +LightmapSettings: + m_ObjectHideFlags: 0 + serializedVersion: 11 + m_GIWorkflowMode: 0 + m_GISettings: + serializedVersion: 2 + m_BounceScale: 1 + m_IndirectOutputScale: 1 + m_AlbedoBoost: 1 + m_EnvironmentLightingMode: 0 + m_EnableBakedLightmaps: 1 + m_EnableRealtimeLightmaps: 1 + m_LightmapEditorSettings: + serializedVersion: 10 + m_Resolution: 2 + m_BakeResolution: 40 + m_AtlasSize: 1024 + m_AO: 0 + m_AOMaxDistance: 1 + m_CompAOExponent: 1 + m_CompAOExponentDirect: 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: 500 + m_PVRBounces: 2 + m_PVRFilterTypeDirect: 0 + m_PVRFilterTypeIndirect: 0 + m_PVRFilterTypeAO: 0 + m_PVRFilteringMode: 1 + m_PVRCulling: 1 + m_PVRFilteringGaussRadiusDirect: 1 + m_PVRFilteringGaussRadiusIndirect: 5 + m_PVRFilteringGaussRadiusAO: 2 + m_PVRFilteringAtrousPositionSigmaDirect: 0.5 + m_PVRFilteringAtrousPositionSigmaIndirect: 2 + m_PVRFilteringAtrousPositionSigmaAO: 1 + m_ShowResolutionOverlay: 1 + m_LightingDataAsset: {fileID: 0} + m_UseShadowmask: 1 +--- !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 + debug: + m_Flags: 0 + m_NavMeshData: {fileID: 0} +--- !u!1 &8790187 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8790189} + - component: {fileID: 8790188} + 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 &8790188 +Light: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8790187} + m_Enabled: 1 + serializedVersion: 8 + m_Type: 1 + m_Color: {r: 1, g: 0.95686275, b: 0.8392157, a: 1} + m_Intensity: 1 + m_Range: 10 + m_SpotAngle: 30 + 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_Cookie: {fileID: 0} + m_DrawHalo: 0 + m_Flare: {fileID: 0} + m_RenderMode: 0 + m_CullingMask: + serializedVersion: 2 + m_Bits: 4294967295 + m_Lightmapping: 4 + m_LightShadowCasterMode: 0 + m_AreaSize: {x: 1, y: 1} + m_BounceIntensity: 1 + m_ColorTemperature: 6570 + m_UseColorTemperature: 0 + m_ShadowRadius: 0 + m_ShadowAngle: 0 +--- !u!4 &8790189 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 8790187} + 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!1 &324336641 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 324336643} + - component: {fileID: 324336642} + m_Layer: 0 + m_Name: SceneNetworkIdentity + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!114 &324336642 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 324336641} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + sceneId: 3353204156 + serverOnly: 0 + m_AssetId: + hasSpawned: 0 +--- !u!4 &324336643 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 324336641} + 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: 3 + m_LocalEulerAnglesHint: {x: 0, y: 0, z: 0} +--- !u!1 &417599908 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 417599911} + - component: {fileID: 417599910} + - component: {fileID: 417599909} + 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 &417599909 +AudioListener: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 417599908} + m_Enabled: 1 +--- !u!20 &417599910 +Camera: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 417599908} + 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_SensorSize: {x: 36, y: 24} + m_LensShift: {x: 0, y: 0} + m_GateFitMode: 2 + 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 &417599911 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 417599908} + m_LocalRotation: {x: 0, y: 0, z: 0, w: 1} + m_LocalPosition: {x: 0, y: 1, z: -10} + 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!1001 &330407922257146537 +PrefabInstance: + m_ObjectHideFlags: 0 + serializedVersion: 2 + m_Modification: + m_TransformParent: {fileID: 0} + m_Modifications: + - target: {fileID: 4064655512664549480, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_Name + value: TestNetworkManager + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalPosition.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalPosition.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalPosition.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalRotation.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalRotation.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalRotation.z + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalRotation.w + value: 1 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_RootOrder + value: 2 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalEulerAnglesHint.x + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalEulerAnglesHint.y + value: 0 + objectReference: {fileID: 0} + - target: {fileID: 8279465151912833971, guid: b243904f0fa5b04418e0e135533686b0, + type: 3} + propertyPath: m_LocalEulerAnglesHint.z + value: 0 + objectReference: {fileID: 0} + m_RemovedComponents: [] + m_SourcePrefab: {fileID: 100100000, guid: b243904f0fa5b04418e0e135533686b0, type: 3} diff --git a/Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity.meta b/Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity.meta new file mode 100644 index 000000000..65474c733 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes/SceneObjectSpawningTestsScene.unity.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 11c6caa4a9760bf4a909e72138dfb8e5 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab b/Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab new file mode 100644 index 000000000..1eabd8217 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab @@ -0,0 +1,114 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &4064655512664549480 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 8279465151912833971} + - component: {fileID: 1233094718765946942} + - component: {fileID: 2509988586038888270} + m_Layer: 0 + m_Name: TestNetworkManager + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &8279465151912833971 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4064655512664549480} + 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 &1233094718765946942 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4064655512664549480} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 8aab4c8111b7c411b9b92cf3dbc5bd4e, type: 3} + m_Name: + m_EditorClassIdentifier: + dontDestroyOnLoad: 0 + runInBackground: 0 + startOnHeadless: 0 + showDebugMessages: 0 + serverTickRate: 30 + offlineScene: + onlineScene: + transport: {fileID: 2509988586038888270} + networkAddress: localhost + maxConnections: 4 + disconnectInactiveConnections: 0 + disconnectInactiveTimeout: 60 + authenticator: {fileID: 0} + playerPrefab: {fileID: 796411093575107534, guid: 5d01c4bf42a9b434dac396c2ba1aea10, + type: 3} + autoCreatePlayer: 0 + playerSpawnMethod: 0 + spawnPrefabs: [] +--- !u!114 &2509988586038888270 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 4064655512664549480} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 6131cf1e8a1c14ef5b5253f86f3fc9c9, type: 3} + m_Name: + m_EditorClassIdentifier: + OnClientConnected: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0, + Culture=neutral, PublicKeyToken=null + OnClientDataReceived: + m_PersistentCalls: + m_Calls: [] + m_TypeName: Mirror.ClientDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + OnClientError: + m_PersistentCalls: + m_Calls: [] + m_TypeName: Mirror.UnityEventException, Mirror, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + OnClientDisconnected: + m_PersistentCalls: + m_Calls: [] + m_TypeName: UnityEngine.Events.UnityEvent, UnityEngine.CoreModule, Version=0.0.0.0, + Culture=neutral, PublicKeyToken=null + OnServerConnected: + m_PersistentCalls: + m_Calls: [] + m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null + OnServerDataReceived: + m_PersistentCalls: + m_Calls: [] + m_TypeName: Mirror.ServerDataReceivedEvent, Mirror, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + OnServerError: + m_PersistentCalls: + m_Calls: [] + m_TypeName: Mirror.UnityEventIntException, Mirror, Version=0.0.0.0, Culture=neutral, + PublicKeyToken=null + OnServerDisconnected: + m_PersistentCalls: + m_Calls: [] + m_TypeName: Mirror.UnityEventInt, Mirror, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null diff --git a/Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab.meta b/Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab.meta new file mode 100644 index 000000000..ac5d48478 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes/TestNetworkManager.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: b243904f0fa5b04418e0e135533686b0 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab b/Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab new file mode 100644 index 000000000..77421a8c5 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab @@ -0,0 +1,48 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!1 &796411093575107534 +GameObject: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + serializedVersion: 6 + m_Component: + - component: {fileID: 6042682300829753709} + - component: {fileID: 9021640260471491865} + m_Layer: 0 + m_Name: TestPlayer + m_TagString: Untagged + m_Icon: {fileID: 0} + m_NavMeshLayer: 0 + m_StaticEditorFlags: 0 + m_IsActive: 1 +--- !u!4 &6042682300829753709 +Transform: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 796411093575107534} + 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 &9021640260471491865 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 796411093575107534} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 9b91ecbcc199f4492b9a91e820070131, type: 3} + m_Name: + m_EditorClassIdentifier: + sceneId: 0 + serverOnly: 0 + m_AssetId: 5d01c4bf42a9b434dac396c2ba1aea10 diff --git a/Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab.meta b/Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab.meta new file mode 100644 index 000000000..949e8a566 --- /dev/null +++ b/Assets/Mirror/Tests/Runtime/Scenes/TestPlayer.prefab.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5d01c4bf42a9b434dac396c2ba1aea10 +PrefabImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: