From d64ff145cdfacc25da9606b6ddcf476ac6298bfb Mon Sep 17 00:00:00 2001 From: Mike Dickson Date: Tue, 6 Aug 2024 11:14:16 -0400 Subject: [PATCH] Feature/lsd fix (#83) * Placeholders for AISv3. EF Core Model code in OpenSim.Data.Models. Both projects are in the Source directory and not yet included in the solution. Tests are added back in Tests. Still need to Add the LinkSet Data tests and activate them in the solution. * Add PBR Terrain fields to ef core regionsettings table. Add OpenSIm.Data.Models to solution. * Restructure the LinksetData Deserialize. We'll also save the size value so it doesnt need to be calculated. We make the size a required field so we can tell if its not present and fall back to handling the old format as well. Make the serialized fields writable publically so they can be deserialized successfully. This is a change in dotnet 8. * Address CodeQL comments. Ternary operator in LinksetData Deserialize and rename some generated code for the Search tables ef core support away from Object which is of course a reserved word. --- .gitignore | 5 + Directory.Build.props | 17 +- BUILDING.md => Docs/BUILDING.md | 0 CONTRIBUTORS.txt => Docs/CONTRIBUTORS.txt | 1 - RELEASE.md => Docs/RELEASE.md | 0 SECURITY.md => Docs/SECURITY.md | 0 TESTING.txt => Docs/TESTING.txt | 0 OpenSim.sln | 20 + .../Addons/Groups/Service/GroupsService.cs | 4 +- OpenSim/Data/MySQL/MySQLSimulationData.cs | 9 +- OpenSim/Data/PGSQL/PGSQLSimulationData.cs | 10 +- OpenSim/Data/SQLite/SQLiteSimulationData.cs | 10 +- .../Monitoring/AssetStatsCollector.cs | 2 +- .../Monitoring/Properties/AssemblyInfo.cs | 33 - .../Monitoring/SimExtraStatsCollector.cs | 2 +- OpenSim/Framework/Monitoring/StatsLogger.cs | 4 +- .../Monitoring/UserStatsCollector.cs | 2 +- OpenSim/Framework/VersionInfo.cs | 2 +- .../Linden/Caps/UploadBakedTextureModule.cs | 4 +- .../Region/Framework/Scenes/LinksetData.cs | 168 +- OpenSim/Region/Framework/Scenes/Scene.cs | 3 - .../Framework/Scenes/SceneObjectGroup.cs | 34 +- .../Framework/Scenes/SceneObjectPart.cs | 548 +- .../Serialization/SceneObjectSerializer.cs | 7 +- .../UserStatistics/WebStatsModule.cs | 6 +- .../World/AutoBackup/AutoBackupModule.cs | 4 +- .../Shared/Api/Implementation/LSL_Api.cs | 6 +- Source/OpenSim.Data.Model/Core/AgentPref.cs | 23 + Source/OpenSim.Data.Model/Core/Asset.cs | 29 + Source/OpenSim.Data.Model/Core/Auth.cs | 17 + Source/OpenSim.Data.Model/Core/Avatar.cs | 13 + Source/OpenSim.Data.Model/Core/Classified.cs | 37 + Source/OpenSim.Data.Model/Core/EstateGroup.cs | 11 + .../OpenSim.Data.Model/Core/EstateManager.cs | 11 + Source/OpenSim.Data.Model/Core/EstateMap.cs | 11 + .../OpenSim.Data.Model/Core/EstateSetting.cs | 63 + Source/OpenSim.Data.Model/Core/EstateUser.cs | 11 + Source/OpenSim.Data.Model/Core/Estateban.cs | 21 + Source/OpenSim.Data.Model/Core/Friend.cs | 15 + Source/OpenSim.Data.Model/Core/Fsasset.cs | 23 + .../Core/GloebitSubscription.cs | 23 + .../Core/GloebitTransaction.cs | 61 + Source/OpenSim.Data.Model/Core/GloebitUser.cs | 17 + Source/OpenSim.Data.Model/Core/GridUser.cs | 27 + .../Core/HgTravelingDatum.cs | 21 + Source/OpenSim.Data.Model/Core/ImOffline.cs | 17 + .../Core/Inventoryfolder.cs | 19 + .../OpenSim.Data.Model/Core/Inventoryitem.cs | 47 + Source/OpenSim.Data.Model/Core/Migration.cs | 11 + Source/OpenSim.Data.Model/Core/MuteList.cs | 19 + .../Core/OpenSimCoreContext.cs | 1642 +++ .../OpenSim.Data.Model/Core/OsGroupsGroup.cs | 31 + .../OpenSim.Data.Model/Core/OsGroupsInvite.cs | 17 + .../Core/OsGroupsMembership.cs | 21 + .../OpenSim.Data.Model/Core/OsGroupsNotice.cs | 29 + .../Core/OsGroupsPrincipal.cs | 11 + .../OpenSim.Data.Model/Core/OsGroupsRole.cs | 19 + .../Core/OsGroupsRolemembership.cs | 13 + Source/OpenSim.Data.Model/Core/Presence.cs | 17 + Source/OpenSim.Data.Model/Core/Region.cs | 81 + Source/OpenSim.Data.Model/Core/Token.cs | 13 + Source/OpenSim.Data.Model/Core/UserAccount.cs | 29 + Source/OpenSim.Data.Model/Core/UserAlias.cs | 15 + Source/OpenSim.Data.Model/Core/Userdatum.cs | 15 + Source/OpenSim.Data.Model/Core/Usernote.cs | 13 + Source/OpenSim.Data.Model/Core/Userpick.cs | 35 + Source/OpenSim.Data.Model/Core/Userprofile.cs | 35 + Source/OpenSim.Data.Model/Core/Usersetting.cs | 15 + Source/OpenSim.Data.Model/Economy/Balance.cs | 18 + .../Economy/OpenSimEconomyContext.cs | 164 + .../OpenSim.Data.Model/Economy/Totalsale.cs | 24 + .../OpenSim.Data.Model/Economy/Transaction.cs | 42 + Source/OpenSim.Data.Model/Economy/Userinfo.cs | 24 + .../Identity/EfmigrationsHistory.cs | 11 + .../Identity/IdentityContext.cs | 165 + .../Identity/IdentityRole.cs | 19 + .../Identity/IdentityRoleClaim.cs | 17 + .../Identity/IdentityUser.cs | 51 + .../Identity/IdentityUserClaim.cs | 17 + .../Identity/IdentityUserLogin.cs | 17 + .../Identity/IdentityUserToken.cs | 17 + .../OpenSim.Data.Model.csproj | 30 + .../OpenSim.Data.Model/Region/Bakedterrain.cs | 13 + Source/OpenSim.Data.Model/Region/Land.cs | 95 + .../Region/Landaccesslist.cs | 15 + Source/OpenSim.Data.Model/Region/Migration.cs | 11 + .../Region/OpenSimRegionContext.cs | 863 ++ Source/OpenSim.Data.Model/Region/Prim.cs | 211 + Source/OpenSim.Data.Model/Region/Primitem.cs | 45 + Source/OpenSim.Data.Model/Region/Primshape.cs | 67 + Source/OpenSim.Data.Model/Region/Regionban.cs | 15 + .../Region/Regionenvironment.cs | 11 + .../OpenSim.Data.Model/Region/Regionextra.cs | 13 + .../Region/Regionsetting.cs | 107 + .../Region/Regionwindlight.cs | 133 + .../OpenSim.Data.Model/Region/SpawnPoint.cs | 15 + Source/OpenSim.Data.Model/Region/Terrain.cs | 13 + Source/OpenSim.Data.Model/Search/Allparcel.cs | 25 + .../OpenSim.Data.Model/Search/Classified.cs | 37 + Source/OpenSim.Data.Model/Search/Event.cs | 37 + .../Search/Hostsregister.cs | 21 + .../Search/OpenSimSearchContext.cs | 464 + Source/OpenSim.Data.Model/Search/Parcel.cs | 35 + .../OpenSim.Data.Model/Search/Parcelsale.cs | 29 + .../OpenSim.Data.Model/Search/Popularplace.cs | 21 + Source/OpenSim.Data.Model/Search/Region.cs | 21 + .../OpenSim.Data.Model/Search/SearchObject.cs | 21 + Source/OpenSim.Data.Model/issues.txt | 2 + .../Controllers/WeatherForecastController.cs | 32 + .../OpenSim.Services.AISv3.csproj | 14 + .../OpenSim.Services.AISv3.http | 6 + Source/OpenSim.Services.AISv3/Program.cs | 25 + .../OpenSim.Services.AISv3/WeatherForecast.cs | 12 + .../appsettings.Development.json | 8 + .../OpenSim.Services.AISv3/appsettings.json | 9 + .../Tests/FetchInventory2HandlerTests.cs | 160 + .../FetchInventoryDescendents2HandlerTests.cs | 295 + .../Tests/GetTextureHandlerTests.cs | 65 + ...OpenSim.Capabilities.Handlers.Tests.csproj | 54 + .../AssetsClient.cs | 110 + .../OpenSim.Tests.Clients.AssetClient.csproj | 43 + Tests/OpenSim.Data.Tests/AssetTests.cs | 220 + .../BasicDataServiceTest.cs | 264 + Tests/OpenSim.Data.Tests/DataTestUtil.cs | 88 + Tests/OpenSim.Data.Tests/DefaultTestConns.cs | 84 + Tests/OpenSim.Data.Tests/EstateTests.cs | 511 + Tests/OpenSim.Data.Tests/InventoryTests.cs | 369 + .../OpenSim.Data.Tests.csproj | 50 + .../PropertyCompareConstraint.cs | 413 + Tests/OpenSim.Data.Tests/PropertyScrambler.cs | 184 + Tests/OpenSim.Data.Tests/RegionTests.cs | 1145 ++ .../Resources/TestDataConnections.ini | 24 + .../LLSDTest.cs | 40 + .../LandDataSerializerTests.cs | 162 + ...enSim.Framework.Serialization.Tests.csproj | 49 + .../RegionSettingsSerializerTests.cs | 138 + .../OpenSim.Framework.Servers.Tests.csproj | 36 + .../VersionInfoTests.cs | 45 + .../AgentCircuitDataTest.cs | 350 + .../AgentCircuitManagerTests.cs | 205 + .../OpenSim.Framework.Tests/AnimationTests.cs | 93 + .../OpenSim.Framework.Tests/AssetBaseTest.cs | 75 + Tests/OpenSim.Framework.Tests/CacheTests.cs | 130 + Tests/OpenSim.Framework.Tests/LocationTest.cs | 85 + .../MundaneFrameworkTests.cs | 281 + .../OpenSim.Framework.Tests.csproj | 50 + .../PrimeNumberHelperTests.cs | 135 + Tests/OpenSim.Framework.Tests/UtilTest.cs | 343 + .../NPCPerformanceTests.cs | 182 + .../ObjectPerformanceTests.cs | 169 + .../OpenSim.Tests.Performance.csproj | 58 + .../ScriptPerformanceTests.cs | 155 + Tests/OpenSim.Permissions.Tests/Common.cs | 370 + .../DirectTransferTests.cs | 153 + .../IndirectTransferTests.cs | 132 + .../OpenSim.Tests.Permissions.csproj | 46 + .../EventQueue/Tests/EventQueueTests.cs | 196 + ...Region.ClientStack.LindenCaps.Tests.csproj | 57 + .../Tests/WebFetchInvDescModuleTests.cs | 161 + .../BasicCircuitTests.cs | 266 + .../LLImageManagerTests.cs | 166 + ....Region.ClientStack.LindenUDP.Tests.csproj | 50 + .../PacketHandlerTests.cs | 99 + .../Resources/4-tile2.jp2 | Bin 0 -> 24410 bytes .../ThrottleTests.cs | 424 + .../Asset/Tests/FlotsamAssetCacheTests.cs | 115 + .../Tests/AttachmentsModuleTests.cs | 1029 ++ .../Tests/AvatarFactoryModuleTests.cs | 193 + .../Avatar/Chat/Tests/ChatModuleTests.cs | 313 + .../Avatar/Friends/Tests/FriendModuleTests.cs | 204 + .../Tests/InventoryArchiveLoadPathTests.cs | 351 + .../Tests/InventoryArchiveLoadTests.cs | 182 + .../Tests/InventoryArchiveSaveTests.cs | 412 + .../Tests/InventoryArchiveTestCase.cs | 167 + .../Tests/InventoryTransferModuleTests.cs | 440 + .../Tests/HGAssetMapperTests.cs | 147 + .../Tests/InventoryAccessModuleTests.cs | 166 + .../Tests/HGUserManagementModuleTests.cs | 74 + .../OpenSim.Region.CoreModules.Tests.csproj | 66 + .../Tests/ScriptsHttpRequestsTests.cs | 189 + .../Tests/VectorRenderModuleTests.cs | 311 + .../Asset/Tests/AssetConnectorTests.cs | 163 + .../Grid/Tests/GridConnectorsTests.cs | 192 + .../Presence/Tests/PresenceConnectorsTests.cs | 106 + .../World/Archiver/Tests/ArchiverTests.cs | 1048 ++ .../Archiver/Tests/Resources/test-sound.wav | Bin 0 -> 211648 bytes .../Land/Tests/LandManagementModuleTests.cs | 266 + .../World/Land/Tests/PrimCountModuleTests.cs | 376 + .../World/Media/Moap/Tests/MoapTests.cs | 92 + .../World/Serialiser/Tests/SerialiserTests.cs | 881 ++ .../World/Terrain/Tests/TerrainModuleTests.cs | 75 + .../World/Terrain/Tests/TerrainTest.cs | 119 + .../GlobalUsings.cs | 2 + .../OpenSim.Region.Framework.Tests.csproj | 78 + .../Scenes/Tests/EntityManagerTests.cs | 185 + .../Scenes/Tests/SceneGraphTests.cs | 86 + .../Scenes/Tests/SceneManagerTests.cs | 47 + .../Scenes/Tests/SceneObjectBasicTests.cs | 239 + .../Scenes/Tests/SceneObjectCopyTests.cs | 346 + .../Scenes/Tests/SceneObjectCrossingTests.cs | 294 + .../Scenes/Tests/SceneObjectDeRezTests.cs | 253 + .../Scenes/Tests/SceneObjectLinkingTests.cs | 368 + .../Scenes/Tests/SceneObjectResizeTests.cs | 98 + .../Scenes/Tests/SceneObjectScriptTests.cs | 71 + .../Tests/SceneObjectSerializationTests.cs | 134 + .../Scenes/Tests/SceneObjectSpatialTests.cs | 153 + .../Scenes/Tests/SceneObjectStatusTests.cs | 242 + .../Scenes/Tests/SceneObjectUndoRedoTests.cs | 184 + .../Scenes/Tests/SceneObjectUserGroupTests.cs | 77 + .../Scenes/Tests/ScenePresenceAgentTests.cs | 290 + .../Tests/ScenePresenceAnimationTests.cs | 52 + .../Tests/ScenePresenceAutopilotTests.cs | 124 + .../Tests/ScenePresenceCapabilityTests.cs | 87 + .../Tests/ScenePresenceCrossingTests.cs | 246 + .../Scenes/Tests/ScenePresenceSitTests.cs | 242 + .../Tests/ScenePresenceTeleportTests.cs | 685 + .../Scenes/Tests/SceneStatisticsTests.cs | 69 + .../Scenes/Tests/SceneTelehubTests.cs | 117 + .../Scenes/Tests/SceneTests.cs | 106 + .../Scenes/Tests/SharedRegionModuleTests.cs | 248 + .../Scenes/Tests/TaskInventoryTests.cs | 163 + .../Scenes/Tests/UserInventoryTests.cs | 191 + .../Scenes/Tests/UuidGathererTests.cs | 160 + .../XmlRpcGroups/Tests/GroupsModuleTests.cs | 276 + .../WebSocketEchoTest/WebSocketEchoModule.cs | 175 + .../Tests/JsonStoreScriptModuleTests.cs | 900 ++ .../World/NPC/Tests/NPCModuleTests.cs | 486 + .../BasicVehicles.cs | 152 + .../BulletSimTests.cs | 50 + .../BulletSimTestsUtil.cs | 109 + .../HullCreation.cs | 195 + ....Region.PhysicsModule.BulletS.Tests.csproj | 45 + .../Raycast.cs | 116 + .../OpenSim.Region.ScriptEngine.Tests.csproj | 55 + .../Shared/Tests/LSL_ApiAvatarTests.cs | 147 + .../Shared/Tests/LSL_ApiHttpTests.cs | 245 + .../Shared/Tests/LSL_ApiInventoryTests.cs | 298 + .../Shared/Tests/LSL_ApiLinkingTests.cs | 190 + .../Shared/Tests/LSL_ApiListTests.cs | 138 + .../Shared/Tests/LSL_ApiNotecardTests.cs | 256 + .../Shared/Tests/LSL_ApiObjectTests.cs | 397 + .../Shared/Tests/LSL_ApiTest.cs | 277 + .../Shared/Tests/LSL_ApiUserTests.cs | 156 + .../Shared/Tests/LSL_TypesTestLSLFloat.cs | 661 + .../Shared/Tests/LSL_TypesTestLSLInteger.cs | 150 + .../Shared/Tests/LSL_TypesTestLSLString.cs | 144 + .../Shared/Tests/LSL_TypesTestList.cs | 295 + .../Shared/Tests/LSL_TypesTestVector3.cs | 63 + .../Shared/Tests/OSSL_ApiAppearanceTest.cs | 166 + .../Shared/Tests/OSSL_ApiAttachmentTests.cs | 226 + .../Shared/Tests/OSSL_ApiNpcTests.cs | 343 + .../YEngine/XMREngXmrTestLs.cs | 544 + .../Clients/Grid/GridClient.cs | 133 + .../Clients/Grid/GridForm.html | 11 + .../Clients/InstantMessage/IMClient.cs | 58 + .../Clients/Inventory/InventoryClient.cs | 193 + .../Clients/Presence/PresenceClient.cs | 81 + .../UserAccounts/UserAccountsClient.cs | 86 + .../OpenSim.Robust.Tests/Robust.Tests.csproj | 48 + .../Server/DemonServer.cs | 66 + .../Tests/AssetServerPostHandlerTests.cs | 108 + .../OpenSim.Server.Handlers.Tests.csproj | 57 + ...Sim.Services.InventoryService.Tests.csproj | 56 + .../XInventoryServiceTests.cs | 174 + .../OpenSim.Tests.Stress.csproj | 58 + .../VectorRenderModuleStressTests.cs | 121 + Tests/OpenSim.Tests.Common/GlobalUsings.cs | 2 + .../Helpers/AssetHelpers.cs | 168 + .../Helpers/BaseRequestHandlerHelpers.cs | 85 + .../Helpers/ClientStackHelpers.cs | 95 + .../Helpers/EntityTransferHelpers.cs | 97 + .../Helpers/SceneHelpers.cs | 738 + .../Helpers/TaskInventoryHelpers.cs | 210 + .../Helpers/UserAccountHelpers.cs | 160 + .../Helpers/UserInventoryHelpers.cs | 374 + .../Mock/BaseAssetRepository.cs | 62 + .../Mock/MockAssetDataPlugin.cs | 70 + .../Mock/MockGroupsServicesConnector.cs | 398 + .../Mock/MockRegionDataPlugin.cs | 373 + .../Mock/MockScriptEngine.cs | 298 + Tests/OpenSim.Tests.Common/Mock/TestClient.cs | 1410 ++ .../Mock/TestEventQueueGetModule.cs | 228 + .../Mock/TestGroupsDataPlugin.cs | 339 + .../Mock/TestHttpClientContext.cs | 112 + .../Mock/TestHttpRequest.cs | 175 + .../Mock/TestHttpResponse.cs | 173 + .../Mock/TestInventoryDataPlugin.cs | 216 + .../Mock/TestLLUDPServer.cs | 169 + .../Mock/TestLandChannel.cs | 130 + .../Mock/TestOSHttpRequest.cs | 192 + .../Mock/TestOSHttpResponse.cs | 139 + Tests/OpenSim.Tests.Common/Mock/TestScene.cs | 72 + .../Mock/TestXInventoryDataPlugin.cs | 197 + .../OpenSim.Tests.Common.csproj | 62 + Tests/OpenSim.Tests.Common/OpenSimTestCase.cs | 55 + Tests/OpenSim.Tests.Common/TestHelpers.cs | 112 + Tests/OpenSim.Tests.Common/TestsAssetCache.cs | 142 + Tests/OpenSim.Tests.Common/UnitTest1.cs | 10 + .../OpenSim.Tests/Clients/Grid/GridClient.cs | 193 + .../OpenSim.Tests/ConfigurationLoaderTest.cs | 149 + Tests/OpenSim.Tests/GlobalUsings.cs | 1 + Tests/OpenSim.Tests/OpenSim.Tests.csproj | 25 + Tests/OpenSim.Tests/UnitTest1.cs | 10 + bin/Newtonsoft.Json.dll | Bin 698792 -> 0 bytes bin/Newtonsoft.Json.xml | 11193 ---------------- 305 files changed, 44364 insertions(+), 11630 deletions(-) rename BUILDING.md => Docs/BUILDING.md (100%) rename CONTRIBUTORS.txt => Docs/CONTRIBUTORS.txt (97%) rename RELEASE.md => Docs/RELEASE.md (100%) rename SECURITY.md => Docs/SECURITY.md (100%) rename TESTING.txt => Docs/TESTING.txt (100%) delete mode 100644 OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs create mode 100644 Source/OpenSim.Data.Model/Core/AgentPref.cs create mode 100644 Source/OpenSim.Data.Model/Core/Asset.cs create mode 100644 Source/OpenSim.Data.Model/Core/Auth.cs create mode 100644 Source/OpenSim.Data.Model/Core/Avatar.cs create mode 100644 Source/OpenSim.Data.Model/Core/Classified.cs create mode 100644 Source/OpenSim.Data.Model/Core/EstateGroup.cs create mode 100644 Source/OpenSim.Data.Model/Core/EstateManager.cs create mode 100644 Source/OpenSim.Data.Model/Core/EstateMap.cs create mode 100644 Source/OpenSim.Data.Model/Core/EstateSetting.cs create mode 100644 Source/OpenSim.Data.Model/Core/EstateUser.cs create mode 100644 Source/OpenSim.Data.Model/Core/Estateban.cs create mode 100644 Source/OpenSim.Data.Model/Core/Friend.cs create mode 100644 Source/OpenSim.Data.Model/Core/Fsasset.cs create mode 100644 Source/OpenSim.Data.Model/Core/GloebitSubscription.cs create mode 100644 Source/OpenSim.Data.Model/Core/GloebitTransaction.cs create mode 100644 Source/OpenSim.Data.Model/Core/GloebitUser.cs create mode 100644 Source/OpenSim.Data.Model/Core/GridUser.cs create mode 100644 Source/OpenSim.Data.Model/Core/HgTravelingDatum.cs create mode 100644 Source/OpenSim.Data.Model/Core/ImOffline.cs create mode 100644 Source/OpenSim.Data.Model/Core/Inventoryfolder.cs create mode 100644 Source/OpenSim.Data.Model/Core/Inventoryitem.cs create mode 100644 Source/OpenSim.Data.Model/Core/Migration.cs create mode 100644 Source/OpenSim.Data.Model/Core/MuteList.cs create mode 100644 Source/OpenSim.Data.Model/Core/OpenSimCoreContext.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsGroup.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsInvite.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsMembership.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsNotice.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsPrincipal.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsRole.cs create mode 100644 Source/OpenSim.Data.Model/Core/OsGroupsRolemembership.cs create mode 100644 Source/OpenSim.Data.Model/Core/Presence.cs create mode 100644 Source/OpenSim.Data.Model/Core/Region.cs create mode 100644 Source/OpenSim.Data.Model/Core/Token.cs create mode 100644 Source/OpenSim.Data.Model/Core/UserAccount.cs create mode 100644 Source/OpenSim.Data.Model/Core/UserAlias.cs create mode 100644 Source/OpenSim.Data.Model/Core/Userdatum.cs create mode 100644 Source/OpenSim.Data.Model/Core/Usernote.cs create mode 100644 Source/OpenSim.Data.Model/Core/Userpick.cs create mode 100644 Source/OpenSim.Data.Model/Core/Userprofile.cs create mode 100644 Source/OpenSim.Data.Model/Core/Usersetting.cs create mode 100644 Source/OpenSim.Data.Model/Economy/Balance.cs create mode 100644 Source/OpenSim.Data.Model/Economy/OpenSimEconomyContext.cs create mode 100644 Source/OpenSim.Data.Model/Economy/Totalsale.cs create mode 100644 Source/OpenSim.Data.Model/Economy/Transaction.cs create mode 100644 Source/OpenSim.Data.Model/Economy/Userinfo.cs create mode 100644 Source/OpenSim.Data.Model/Identity/EfmigrationsHistory.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityContext.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityRole.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityRoleClaim.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityUser.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityUserClaim.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityUserLogin.cs create mode 100644 Source/OpenSim.Data.Model/Identity/IdentityUserToken.cs create mode 100644 Source/OpenSim.Data.Model/OpenSim.Data.Model.csproj create mode 100644 Source/OpenSim.Data.Model/Region/Bakedterrain.cs create mode 100644 Source/OpenSim.Data.Model/Region/Land.cs create mode 100644 Source/OpenSim.Data.Model/Region/Landaccesslist.cs create mode 100644 Source/OpenSim.Data.Model/Region/Migration.cs create mode 100644 Source/OpenSim.Data.Model/Region/OpenSimRegionContext.cs create mode 100644 Source/OpenSim.Data.Model/Region/Prim.cs create mode 100644 Source/OpenSim.Data.Model/Region/Primitem.cs create mode 100644 Source/OpenSim.Data.Model/Region/Primshape.cs create mode 100644 Source/OpenSim.Data.Model/Region/Regionban.cs create mode 100644 Source/OpenSim.Data.Model/Region/Regionenvironment.cs create mode 100644 Source/OpenSim.Data.Model/Region/Regionextra.cs create mode 100644 Source/OpenSim.Data.Model/Region/Regionsetting.cs create mode 100644 Source/OpenSim.Data.Model/Region/Regionwindlight.cs create mode 100644 Source/OpenSim.Data.Model/Region/SpawnPoint.cs create mode 100644 Source/OpenSim.Data.Model/Region/Terrain.cs create mode 100644 Source/OpenSim.Data.Model/Search/Allparcel.cs create mode 100644 Source/OpenSim.Data.Model/Search/Classified.cs create mode 100644 Source/OpenSim.Data.Model/Search/Event.cs create mode 100644 Source/OpenSim.Data.Model/Search/Hostsregister.cs create mode 100644 Source/OpenSim.Data.Model/Search/OpenSimSearchContext.cs create mode 100644 Source/OpenSim.Data.Model/Search/Parcel.cs create mode 100644 Source/OpenSim.Data.Model/Search/Parcelsale.cs create mode 100644 Source/OpenSim.Data.Model/Search/Popularplace.cs create mode 100644 Source/OpenSim.Data.Model/Search/Region.cs create mode 100644 Source/OpenSim.Data.Model/Search/SearchObject.cs create mode 100644 Source/OpenSim.Data.Model/issues.txt create mode 100644 Source/OpenSim.Services.AISv3/Controllers/WeatherForecastController.cs create mode 100644 Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.csproj create mode 100644 Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.http create mode 100644 Source/OpenSim.Services.AISv3/Program.cs create mode 100644 Source/OpenSim.Services.AISv3/WeatherForecast.cs create mode 100644 Source/OpenSim.Services.AISv3/appsettings.Development.json create mode 100644 Source/OpenSim.Services.AISv3/appsettings.json create mode 100644 Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventory2HandlerTests.cs create mode 100644 Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs create mode 100644 Tests/OpenSim.Capabilities.Handlers.Tests/GetTexture/Tests/GetTextureHandlerTests.cs create mode 100644 Tests/OpenSim.Capabilities.Handlers.Tests/OpenSim.Capabilities.Handlers.Tests.csproj create mode 100644 Tests/OpenSim.Clients.Assets.Tests/AssetsClient.cs create mode 100644 Tests/OpenSim.Clients.Assets.Tests/OpenSim.Tests.Clients.AssetClient.csproj create mode 100644 Tests/OpenSim.Data.Tests/AssetTests.cs create mode 100644 Tests/OpenSim.Data.Tests/BasicDataServiceTest.cs create mode 100644 Tests/OpenSim.Data.Tests/DataTestUtil.cs create mode 100644 Tests/OpenSim.Data.Tests/DefaultTestConns.cs create mode 100644 Tests/OpenSim.Data.Tests/EstateTests.cs create mode 100644 Tests/OpenSim.Data.Tests/InventoryTests.cs create mode 100644 Tests/OpenSim.Data.Tests/OpenSim.Data.Tests.csproj create mode 100644 Tests/OpenSim.Data.Tests/PropertyCompareConstraint.cs create mode 100644 Tests/OpenSim.Data.Tests/PropertyScrambler.cs create mode 100644 Tests/OpenSim.Data.Tests/RegionTests.cs create mode 100644 Tests/OpenSim.Data.Tests/Resources/TestDataConnections.ini create mode 100644 Tests/OpenSim.Framework.Capabilities.Tests/LLSDTest.cs create mode 100644 Tests/OpenSim.Framework.Serialization.Tests/LandDataSerializerTests.cs create mode 100644 Tests/OpenSim.Framework.Serialization.Tests/OpenSim.Framework.Serialization.Tests.csproj create mode 100644 Tests/OpenSim.Framework.Serialization.Tests/RegionSettingsSerializerTests.cs create mode 100644 Tests/OpenSim.Framework.Servers.Tests/OpenSim.Framework.Servers.Tests.csproj create mode 100644 Tests/OpenSim.Framework.Servers.Tests/VersionInfoTests.cs create mode 100644 Tests/OpenSim.Framework.Tests/AgentCircuitDataTest.cs create mode 100644 Tests/OpenSim.Framework.Tests/AgentCircuitManagerTests.cs create mode 100644 Tests/OpenSim.Framework.Tests/AnimationTests.cs create mode 100644 Tests/OpenSim.Framework.Tests/AssetBaseTest.cs create mode 100644 Tests/OpenSim.Framework.Tests/CacheTests.cs create mode 100644 Tests/OpenSim.Framework.Tests/LocationTest.cs create mode 100644 Tests/OpenSim.Framework.Tests/MundaneFrameworkTests.cs create mode 100644 Tests/OpenSim.Framework.Tests/OpenSim.Framework.Tests.csproj create mode 100644 Tests/OpenSim.Framework.Tests/PrimeNumberHelperTests.cs create mode 100644 Tests/OpenSim.Framework.Tests/UtilTest.cs create mode 100644 Tests/OpenSim.Performance.Tests/NPCPerformanceTests.cs create mode 100644 Tests/OpenSim.Performance.Tests/ObjectPerformanceTests.cs create mode 100644 Tests/OpenSim.Performance.Tests/OpenSim.Tests.Performance.csproj create mode 100644 Tests/OpenSim.Performance.Tests/ScriptPerformanceTests.cs create mode 100644 Tests/OpenSim.Permissions.Tests/Common.cs create mode 100644 Tests/OpenSim.Permissions.Tests/DirectTransferTests.cs create mode 100644 Tests/OpenSim.Permissions.Tests/IndirectTransferTests.cs create mode 100644 Tests/OpenSim.Permissions.Tests/OpenSim.Tests.Permissions.csproj create mode 100644 Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/EventQueue/Tests/EventQueueTests.cs create mode 100644 Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/OpenSim.Region.ClientStack.LindenCaps.Tests.csproj create mode 100644 Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/Tests/WebFetchInvDescModuleTests.cs create mode 100644 Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/BasicCircuitTests.cs create mode 100644 Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/LLImageManagerTests.cs create mode 100644 Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/OpenSim.Region.ClientStack.LindenUDP.Tests.csproj create mode 100644 Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/PacketHandlerTests.cs create mode 100644 Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/Resources/4-tile2.jp2 create mode 100644 Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/ThrottleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Asset/Tests/FlotsamAssetCacheTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Attachments/Tests/AttachmentsModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Chat/Tests/ChatModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Friends/Tests/FriendModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/OpenSim.Region.CoreModules.Tests.csproj create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Presence/Tests/PresenceConnectorsTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/ArchiverTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/Resources/test-sound.wav create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/LandManagementModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/PrimCountModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Media/Moap/Tests/MoapTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Serialiser/Tests/SerialiserTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainModuleTests.cs create mode 100644 Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainTest.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/GlobalUsings.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/OpenSim.Region.Framework.Tests.csproj create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/EntityManagerTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneGraphTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneManagerTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectBasicTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCopyTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCrossingTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectDeRezTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectLinkingTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectResizeTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectScriptTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSerializationTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSpatialTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectStatusTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUndoRedoTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUserGroupTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAgentTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAnimationTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAutopilotTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCapabilityTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCrossingTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceSitTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceTeleportTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneStatisticsTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTelehubTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SharedRegionModuleTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/TaskInventoryTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UserInventoryTests.cs create mode 100644 Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UuidGathererTests.cs create mode 100644 Tests/OpenSim.Region.OptionalModules.Tests/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs create mode 100644 Tests/OpenSim.Region.OptionalModules.Tests/Example/WebSocketEchoTest/WebSocketEchoModule.cs create mode 100644 Tests/OpenSim.Region.OptionalModules.Tests/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs create mode 100644 Tests/OpenSim.Region.OptionalModules.Tests/World/NPC/Tests/NPCModuleTests.cs create mode 100644 Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BasicVehicles.cs create mode 100644 Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTests.cs create mode 100644 Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTestsUtil.cs create mode 100644 Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/HullCreation.cs create mode 100644 Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/OpenSim.Region.PhysicsModule.BulletS.Tests.csproj create mode 100644 Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/Raycast.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/OpenSim.Region.ScriptEngine.Tests.csproj create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiAvatarTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiHttpTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiInventoryTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiLinkingTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiListTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiNotecardTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiObjectTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiTest.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiUserTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLFloat.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLInteger.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLString.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestList.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestVector3.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAppearanceTest.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAttachmentTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiNpcTests.cs create mode 100644 Tests/OpenSim.Region.ScriptEngine.Tests/YEngine/XMREngXmrTestLs.cs create mode 100644 Tests/OpenSim.Robust.Tests/Clients/Grid/GridClient.cs create mode 100644 Tests/OpenSim.Robust.Tests/Clients/Grid/GridForm.html create mode 100644 Tests/OpenSim.Robust.Tests/Clients/InstantMessage/IMClient.cs create mode 100644 Tests/OpenSim.Robust.Tests/Clients/Inventory/InventoryClient.cs create mode 100644 Tests/OpenSim.Robust.Tests/Clients/Presence/PresenceClient.cs create mode 100644 Tests/OpenSim.Robust.Tests/Clients/UserAccounts/UserAccountsClient.cs create mode 100644 Tests/OpenSim.Robust.Tests/Robust.Tests.csproj create mode 100644 Tests/OpenSim.Robust.Tests/Server/DemonServer.cs create mode 100644 Tests/OpenSim.Server.Handlers.Tests/Asset/Tests/AssetServerPostHandlerTests.cs create mode 100644 Tests/OpenSim.Server.Handlers.Tests/OpenSim.Server.Handlers.Tests.csproj create mode 100644 Tests/OpenSim.Services.InventoryService.Tests/OpenSim.Services.InventoryService.Tests.csproj create mode 100644 Tests/OpenSim.Services.InventoryService.Tests/XInventoryServiceTests.cs create mode 100644 Tests/OpenSim.Stress.Tests/OpenSim.Tests.Stress.csproj create mode 100644 Tests/OpenSim.Stress.Tests/VectorRenderModuleStressTests.cs create mode 100644 Tests/OpenSim.Tests.Common/GlobalUsings.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/AssetHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/BaseRequestHandlerHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/ClientStackHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/EntityTransferHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/SceneHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/TaskInventoryHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/UserAccountHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Helpers/UserInventoryHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/BaseAssetRepository.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/MockAssetDataPlugin.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/MockGroupsServicesConnector.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/MockRegionDataPlugin.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/MockScriptEngine.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestClient.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestEventQueueGetModule.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestGroupsDataPlugin.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestHttpClientContext.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestHttpRequest.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestHttpResponse.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestInventoryDataPlugin.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestLLUDPServer.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestLandChannel.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestOSHttpRequest.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestOSHttpResponse.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestScene.cs create mode 100644 Tests/OpenSim.Tests.Common/Mock/TestXInventoryDataPlugin.cs create mode 100644 Tests/OpenSim.Tests.Common/OpenSim.Tests.Common.csproj create mode 100644 Tests/OpenSim.Tests.Common/OpenSimTestCase.cs create mode 100644 Tests/OpenSim.Tests.Common/TestHelpers.cs create mode 100644 Tests/OpenSim.Tests.Common/TestsAssetCache.cs create mode 100644 Tests/OpenSim.Tests.Common/UnitTest1.cs create mode 100644 Tests/OpenSim.Tests/Clients/Grid/GridClient.cs create mode 100644 Tests/OpenSim.Tests/ConfigurationLoaderTest.cs create mode 100644 Tests/OpenSim.Tests/GlobalUsings.cs create mode 100644 Tests/OpenSim.Tests/OpenSim.Tests.csproj create mode 100644 Tests/OpenSim.Tests/UnitTest1.cs delete mode 100644 bin/Newtonsoft.Json.dll delete mode 100755 bin/Newtonsoft.Json.xml diff --git a/.gitignore b/.gitignore index f55477767de..2df17e8cc74 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,5 @@ +OpenSimConsoleHistory.txt + .project .settings .gitignore @@ -19,6 +21,9 @@ launchSettings.json build/ +*/bin/Debug/* +*/bin/Release/* + packages/ Prebuild/src/bin diff --git a/Directory.Build.props b/Directory.Build.props index b8f641b3e23..67c3ff10d51 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,20 +1,23 @@ - false - disable + net8.0 + enable disable + false false true - $(SolutionDir)\obj\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\obj\$(Configuration)\$(MSBuildProjectName)\ - $(SolutionDir)\build\$(Configuration)\$(AssemblyName)\ + + + $(SolutionDir)/obj/$(Configuration)/$(MSBuildProjectName)/ + $(SolutionDir)/obj/$(Configuration)/$(MSBuildProjectName)/ + $(SolutionDir)/build/$(Configuration)/$(AssemblyName)/ $(OutputPath) diff --git a/BUILDING.md b/Docs/BUILDING.md similarity index 100% rename from BUILDING.md rename to Docs/BUILDING.md diff --git a/CONTRIBUTORS.txt b/Docs/CONTRIBUTORS.txt similarity index 97% rename from CONTRIBUTORS.txt rename to Docs/CONTRIBUTORS.txt index cab8a3db5c0..a62969c339d 100644 --- a/CONTRIBUTORS.txt +++ b/Docs/CONTRIBUTORS.txt @@ -241,7 +241,6 @@ This software uses components from the following developers: * log4net (http://logging.apache.org/log4net/) * GlynnTucker.Cache (http://gtcache.sourceforge.net/) * NDesk.Options 0.2.1 (http://www.ndesk.org/Options) -* Json.NET 3.5 Release 6. The binary used is actually Newtonsoft.Json.Net20.dll for Mono 2.4 compatability (http://james.newtonking.com/projects/json-net.aspx) * zlib.net for C# 1.0.4 (http://www.componentace.com/zlib_.NET.htm) Some plugins are based on Cable Beach diff --git a/RELEASE.md b/Docs/RELEASE.md similarity index 100% rename from RELEASE.md rename to Docs/RELEASE.md diff --git a/SECURITY.md b/Docs/SECURITY.md similarity index 100% rename from SECURITY.md rename to Docs/SECURITY.md diff --git a/TESTING.txt b/Docs/TESTING.txt similarity index 100% rename from TESTING.txt rename to Docs/TESTING.txt diff --git a/OpenSim.sln b/OpenSim.sln index 1264c9ea0f9..02a109d13de 100644 --- a/OpenSim.sln +++ b/OpenSim.sln @@ -147,6 +147,14 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSim.Region.OptionalModu EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "OpenSim.Server.MoneyServer", "addon-modules\OpenSim.Server.MoneyServer\OpenSim.Server.MoneyServer\OpenSim.Server.MoneyServer.csproj", "{C3AEE6AC-BE40-4003-A7A3-E8AA039E4451}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{7F5EC7C6-FD99-4084-8530-5D3F28D6D7C0}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSim.Tests.Common", "Tests\OpenSim.Tests.Common\OpenSim.Tests.Common.csproj", "{4B7616BC-6CCE-4B78-AE40-1F24C92442F5}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Source", "Source", "{E886CEB5-B01D-411F-A7A0-72AA1B71761D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "OpenSim.Data.Model", "Source\OpenSim.Data.Model\OpenSim.Data.Model.csproj", "{B120B128-D6C6-40B6-9D83-4F1F7D921109}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -441,10 +449,22 @@ Global {C3AEE6AC-BE40-4003-A7A3-E8AA039E4451}.Debug|Any CPU.Build.0 = Debug|Any CPU {C3AEE6AC-BE40-4003-A7A3-E8AA039E4451}.Release|Any CPU.ActiveCfg = Release|Any CPU {C3AEE6AC-BE40-4003-A7A3-E8AA039E4451}.Release|Any CPU.Build.0 = Release|Any CPU + {4B7616BC-6CCE-4B78-AE40-1F24C92442F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B7616BC-6CCE-4B78-AE40-1F24C92442F5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B7616BC-6CCE-4B78-AE40-1F24C92442F5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B7616BC-6CCE-4B78-AE40-1F24C92442F5}.Release|Any CPU.Build.0 = Release|Any CPU + {B120B128-D6C6-40B6-9D83-4F1F7D921109}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B120B128-D6C6-40B6-9D83-4F1F7D921109}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B120B128-D6C6-40B6-9D83-4F1F7D921109}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B120B128-D6C6-40B6-9D83-4F1F7D921109}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {4B7616BC-6CCE-4B78-AE40-1F24C92442F5} = {7F5EC7C6-FD99-4084-8530-5D3F28D6D7C0} + {B120B128-D6C6-40B6-9D83-4F1F7D921109} = {E886CEB5-B01D-411F-A7A0-72AA1B71761D} + EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {A40AD54B-8BC7-41E4-B241-19B59EA65D2A} EndGlobalSection diff --git a/OpenSim/Addons/Groups/Service/GroupsService.cs b/OpenSim/Addons/Groups/Service/GroupsService.cs index a54b182174f..9e316a5ffde 100644 --- a/OpenSim/Addons/Groups/Service/GroupsService.cs +++ b/OpenSim/Addons/Groups/Service/GroupsService.cs @@ -103,7 +103,7 @@ public class GroupsService : GroupsServiceBase #region Daily Cleanup - private Timer m_CleanupTimer; + private System.Timers.Timer m_CleanupTimer; public GroupsService(IConfigSource config, string configName) : base(config, configName) @@ -114,7 +114,7 @@ public GroupsService(IConfigSource config) : this(config, string.Empty) { // Once a day - m_CleanupTimer = new Timer(24 * 60 * 60 * 1000); + m_CleanupTimer = new System.Timers.Timer(24 * 60 * 60 * 1000); m_CleanupTimer.AutoReset = true; m_CleanupTimer.Elapsed += new ElapsedEventHandler(m_CleanupTimer_Elapsed); m_CleanupTimer.Enabled = true; diff --git a/OpenSim/Data/MySQL/MySQLSimulationData.cs b/OpenSim/Data/MySQL/MySQLSimulationData.cs index c7d854d4f96..6a939a70372 100644 --- a/OpenSim/Data/MySQL/MySQLSimulationData.cs +++ b/OpenSim/Data/MySQL/MySQLSimulationData.cs @@ -1184,10 +1184,11 @@ private SceneObjectPart BuildPrim(IDataReader row) int pseudocrc = (int)row["pseudocrc"]; if(pseudocrc != 0) prim.PseudoCRC = pseudocrc; - - if (!(row["linksetdata"] is DBNull)) + + prim.LinksetData = null; + if (row["linksetdata"] is not DBNull) { - prim.DeserializeLinksetData((string)row["linksetdata"]); + prim.LinksetData = LinksetData.DeserializeLinksetData((string)row["linksetdata"]); } return prim; @@ -1616,7 +1617,7 @@ private void FillPrimCommand(MySqlCommand cmd, SceneObjectPart prim, UUID sceneG cmd.Parameters.AddWithValue("sitactrange", prim.SitActiveRange); cmd.Parameters.AddWithValue("pseudocrc", prim.PseudoCRC); - if (prim.IsRoot && prim.LinksetData is not null) + if (prim.LinksetData is not null) { cmd.Parameters.AddWithValue("linksetdata", prim.SerializeLinksetData()); } diff --git a/OpenSim/Data/PGSQL/PGSQLSimulationData.cs b/OpenSim/Data/PGSQL/PGSQLSimulationData.cs index 534b7a55cf4..1103df3e952 100755 --- a/OpenSim/Data/PGSQL/PGSQLSimulationData.cs +++ b/OpenSim/Data/PGSQL/PGSQLSimulationData.cs @@ -1408,9 +1408,13 @@ private static SceneObjectPart BuildPrim(IDataRecord primRow) prim.Animations = null; } - if ((primRow["LinksetData"] is DBNull) == false) + if (primRow["LinksetData"] is DBNull) { - prim.DeserializeLinksetData(((string)primRow["linksetdata"])); + prim.LinksetData = null; + } + else + { + prim.LinksetData = LinksetData.DeserializeLinksetData(((string)primRow["linksetdata"])); } return prim; @@ -1886,7 +1890,7 @@ private NpgsqlParameter[] CreatePrimParameters(SceneObjectPart prim, UUID sceneG else parameters.Add(_Database.CreateParameterNullBytea("sopanims")); - if (prim.IsRoot && prim.LinksetData is not null) + if (prim.LinksetData is not null) parameters.Add(_Database.CreateParameter("linksetdata", prim.SerializeLinksetData())); else parameters.Add(_Database.CreateParameter("linksetdata", null)); diff --git a/OpenSim/Data/SQLite/SQLiteSimulationData.cs b/OpenSim/Data/SQLite/SQLiteSimulationData.cs index afb490f3d22..24c8d8b330f 100644 --- a/OpenSim/Data/SQLite/SQLiteSimulationData.cs +++ b/OpenSim/Data/SQLite/SQLiteSimulationData.cs @@ -1804,9 +1804,13 @@ private SceneObjectPart buildPrim(DataRow row) prim.Animations = null; } - if (!(row["linksetdata"] is DBNull)) + if (row["linksetdata"] is DBNull) { - prim.DeserializeLinksetData((string)row["LinksetData"]); + prim.LinksetData = null; + } + else + { + prim.LinksetData = LinksetData.DeserializeLinksetData((string)row["LinksetData"]); } return prim; @@ -2207,7 +2211,7 @@ private static void fillPrimRow(DataRow row, SceneObjectPart prim, UUID sceneGro row["pseudocrc"] = prim.PseudoCRC; row["sopanims"] = prim.SerializeAnimations(); - if (prim.IsRoot && prim.LinksetData is not null) + if (prim.LinksetData is not null) row["linksetdata"] = prim.SerializeLinksetData(); else row["linksetdata"] = null; diff --git a/OpenSim/Framework/Monitoring/AssetStatsCollector.cs b/OpenSim/Framework/Monitoring/AssetStatsCollector.cs index 6a0f676b42f..a11b308b914 100644 --- a/OpenSim/Framework/Monitoring/AssetStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/AssetStatsCollector.cs @@ -37,7 +37,7 @@ namespace OpenSim.Framework.Monitoring /// public class AssetStatsCollector : BaseStatsCollector { - private Timer ageStatsTimer = new Timer(24 * 60 * 60 * 1000); + private System.Timers.Timer ageStatsTimer = new System.Timers.Timer(24 * 60 * 60 * 1000); private DateTime startTime = DateTime.Now; private long assetRequestsToday; diff --git a/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs b/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs deleted file mode 100644 index 2ff2014b1ac..00000000000 --- a/OpenSim/Framework/Monitoring/Properties/AssemblyInfo.cs +++ /dev/null @@ -1,33 +0,0 @@ -using System.Reflection; -using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; - -// General Information about an assembly is controlled through the following -// set of attributes. Change these attribute values to modify the information -// associated with an assembly. -[assembly: AssemblyTitle("OpenSim.Framework.Monitoring")] -[assembly: AssemblyDescription("")] -[assembly: AssemblyConfiguration("")] -[assembly: AssemblyCompany("http://opensimulator.org")] -[assembly: AssemblyProduct("OpenSim")] -[assembly: AssemblyCopyright("OpenSimulator developers")] -[assembly: AssemblyTrademark("")] -[assembly: AssemblyCulture("")] - -// Setting ComVisible to false makes the types in this assembly not visible -// to COM components. If you need to access a type in this assembly from -// COM, set the ComVisible attribute to true on that type. -[assembly: ComVisible(false)] - -// The following GUID is for the ID of the typelib if this project is exposed to COM -[assembly: Guid("74506fe3-2f9d-44c1-94c9-a30f79d9e0cb")] - -// Version information for an assembly consists of the following four values: -// -// Major Version -// Minor Version -// Build Number -// Revision -// -[assembly: AssemblyVersion(OpenSim.VersionInfo.AssemblyVersionNumber)] - diff --git a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs index 85c0709cd51..e01c8bf2772 100755 --- a/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/SimExtraStatsCollector.cs @@ -182,7 +182,7 @@ public override OSDMap OReport(string uptime, string version, string scene) // running foreach (ProcessThread currentThread in p.Threads) { - if (currentThread != null && currentThread.ThreadState == ThreadState.Running) + if (currentThread != null && currentThread.ThreadState == System.Diagnostics.ThreadState.Running) numberThreadsRunning++; } } diff --git a/OpenSim/Framework/Monitoring/StatsLogger.cs b/OpenSim/Framework/Monitoring/StatsLogger.cs index e87c18844a8..a59cb7796d0 100755 --- a/OpenSim/Framework/Monitoring/StatsLogger.cs +++ b/OpenSim/Framework/Monitoring/StatsLogger.cs @@ -42,7 +42,7 @@ public static class StatsLogger { private static readonly ILog m_statsLog = LogManager.GetLogger("special.StatsLogger"); - private static Timer m_loggingTimer; + private static System.Timers.Timer m_loggingTimer; private static int m_statsLogIntervalMs = 5000; public static void RegisterConsoleCommands(ICommandConsole console) @@ -114,7 +114,7 @@ public static void Start() if (m_loggingTimer != null) Stop(); - m_loggingTimer = new Timer(m_statsLogIntervalMs); + m_loggingTimer = new System.Timers.Timer(m_statsLogIntervalMs); m_loggingTimer.AutoReset = false; m_loggingTimer.Elapsed += Log; m_loggingTimer.Start(); diff --git a/OpenSim/Framework/Monitoring/UserStatsCollector.cs b/OpenSim/Framework/Monitoring/UserStatsCollector.cs index 81e0fa4e21e..02b999e8044 100644 --- a/OpenSim/Framework/Monitoring/UserStatsCollector.cs +++ b/OpenSim/Framework/Monitoring/UserStatsCollector.cs @@ -36,7 +36,7 @@ namespace OpenSim.Framework.Monitoring /// public class UserStatsCollector : BaseStatsCollector { - private Timer ageStatsTimer = new Timer(24 * 60 * 60 * 1000); + private System.Timers.Timer ageStatsTimer = new System.Timers.Timer(24 * 60 * 60 * 1000); private int successfulLoginsToday; public int SuccessfulLoginsToday { get { return successfulLoginsToday; } } diff --git a/OpenSim/Framework/VersionInfo.cs b/OpenSim/Framework/VersionInfo.cs index 0659c3e3859..48aed85dfa2 100644 --- a/OpenSim/Framework/VersionInfo.cs +++ b/OpenSim/Framework/VersionInfo.cs @@ -39,7 +39,7 @@ public class VersionInfo { public const string VersionNumber = "0.9.3.0"; public const string AssemblyVersionNumber = "0.9.3.0"; - public const string Release = "8940"; + public const string Release = "8984"; public static string Version { diff --git a/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs b/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs index 80515f664bb..e15e176b62c 100644 --- a/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs +++ b/OpenSim/Region/ClientStack/Linden/Caps/UploadBakedTextureModule.cs @@ -160,7 +160,7 @@ class BakedTextureUploader private UUID m_agentID = UUID.Zero; private IPAddress m_remoteAddress; private IAssetCache m_assetCache; - private Timer m_timeout; + private System.Timers.Timer m_timeout; public BakedTextureUploader(string path, IHttpServer httpServer, UUID agentID, IAssetCache cache, IPAddress remoteAddress) { @@ -169,7 +169,7 @@ public BakedTextureUploader(string path, IHttpServer httpServer, UUID agentID, I m_agentID = agentID; m_remoteAddress = remoteAddress; m_assetCache = cache; - m_timeout = new Timer(); + m_timeout = new System.Timers.Timer(); m_timeout.Elapsed += Timeout; m_timeout.AutoReset = false; m_timeout.Interval = 30000; diff --git a/OpenSim/Region/Framework/Scenes/LinksetData.cs b/OpenSim/Region/Framework/Scenes/LinksetData.cs index d314620d256..8d6c13d2cb6 100644 --- a/OpenSim/Region/Framework/Scenes/LinksetData.cs +++ b/OpenSim/Region/Framework/Scenes/LinksetData.cs @@ -1,45 +1,45 @@ using System; using System.Collections.Generic; using System.Linq; -using System.Reflection.Metadata; +using System.Reflection; using System.Text; using System.Text.Json; +using System.Text.Json.Serialization; using System.Text.RegularExpressions; +using ICSharpCode.SharpZipLib.Zip; +using log4net; namespace OpenSim.Region.Framework.Scenes { public class LinksetData : ICloneable { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + public const int LINKSETDATA_MAX = 131072; private static readonly object linksetDataLock = new object(); - public SortedList Data { get; private set; } = new(); + public SortedList Data { get; set; } - public int LinksetDataBytesFree { get; private set; } = LINKSETDATA_MAX; + public int BytesFree { get; set; } - public int LinksetDataBytesUsed { get; private set; } = 0; + /* Make BytesUsed required so we can fail on an old format serialization. + * If we see that we fall back to deserializing the Collection and calculating + * size. This will require it be present in the data going forward + */ + [JsonRequired] + public int BytesUsed { get; set; } public LinksetData() { - Data.Clear(); - LinksetDataBytesFree = LINKSETDATA_MAX; - LinksetDataBytesUsed = 0; + Data = new(); + BytesFree = LINKSETDATA_MAX; + BytesUsed = 0; } public object Clone() { - LinksetData copy = new LinksetData(); - - copy.LinksetDataBytesFree = this.LinksetDataBytesFree; - copy.LinksetDataBytesUsed = this.LinksetDataBytesUsed; - - foreach (KeyValuePair entry in Data) - { - copy.Data.Add(entry.Key, (LinksetDataEntry) entry.Value.Clone()); - } - - return copy; + return LinksetData.DeserializeLinksetData(this.SerializeLinksetData()); } public void Clear() @@ -47,8 +47,8 @@ public void Clear() lock(linksetDataLock) { Data.Clear(); - LinksetDataBytesFree = LINKSETDATA_MAX; - LinksetDataBytesUsed = 0; + BytesFree = LINKSETDATA_MAX; + BytesUsed = 0; } } @@ -64,27 +64,57 @@ public string SerializeLinksetData() { lock (linksetDataLock) { - return JsonSerializer.Serialize>(Data); + return ((Data is null) || (Data.Count <= 0)) ? + null : JsonSerializer.Serialize(this); } } - - public void DeserializeLinksetData(string data) + + public static LinksetData DeserializeLinksetData(string data) { - if (string.IsNullOrWhiteSpace(data)) - return; + LinksetData lsd = null; + + if (string.IsNullOrWhiteSpace(data) is false) + { + try + { + lsd = JsonSerializer.Deserialize(data); + } + catch (JsonException jse) + { + try + { + m_log.Debug($"Exception deserializing LinkSetData, trying original format: {data}", jse); + var listData = JsonSerializer.Deserialize>(data); + lsd = new LinksetData { Data = listData }; + } + catch (Exception e) + { + m_log.Error($"Exception deserializing LinkSetData new and original format: {data}", e); + } + finally + { + // if we got data but no costs calculate that + if (lsd?.BytesUsed <= 0) + { + var cost = 0; - lock (linksetDataLock) - { - Clear(); - - Data = JsonSerializer.Deserialize>(data); + foreach (var kvp in lsd?.Data) + { + cost += kvp.Value.GetCost(kvp.Key); + } - // Handle Cost accounting - foreach (var kvp in Data) + lsd.BytesUsed = cost; + lsd.BytesFree = LINKSETDATA_MAX - cost; + } + } + } + catch (Exception e) { - LinksetDataAccountingDelta(kvp.Value.GetCost(kvp.Key)); + m_log.Error($"General Exception deserializing LinkSetData", e); } - } + } + + return lsd; } /// @@ -120,11 +150,11 @@ public int AddOrUpdateLinksetDataKey(string key, string value, string pass) } // Add New or Update handled here. - LinksetDataEntry newEntry = new LinksetDataEntry(value, pass); - int cost = newEntry.GetCost(key); + var newEntry = new LinksetDataEntry(value, pass); + Data.Add(key, newEntry); + int cost = newEntry.GetCost(key); LinksetDataAccountingDelta(cost); - Data[key] = newEntry; return 0; } @@ -160,6 +190,7 @@ public int DeleteLinksetDataKey(string key, string pass) return 1; Data.Remove(key); + LinksetDataAccountingDelta(-entry.GetCost(key)); return 0; @@ -280,7 +311,7 @@ public bool LinksetDataOverLimit() { lock (linksetDataLock) { - return (LinksetDataBytesFree <= 0); + return (BytesFree <= 0); } } @@ -293,7 +324,7 @@ public bool LinksetDataOverLimit() public void MergeLinksetData(LinksetData otherLinksetData) { // Nothing to merge? - if (otherLinksetData == null) + if (otherLinksetData is null) return; lock (linksetDataLock) @@ -321,8 +352,8 @@ public void MergeLinksetData(LinksetData otherLinksetData) // Clear the LinksetData entries from the "other" SOG otherLinksetData.Data.Clear(); - otherLinksetData.LinksetDataBytesFree = LINKSETDATA_MAX; - otherLinksetData.LinksetDataBytesUsed = 0; + otherLinksetData.BytesFree = LINKSETDATA_MAX; + otherLinksetData.BytesUsed = 0; } } @@ -355,11 +386,11 @@ public string ReadLinksetData(string key, string pass) /// An integer value, positive adds, negative subtracts delta bytes. private void LinksetDataAccountingDelta(int delta) { - LinksetDataBytesUsed += delta; - LinksetDataBytesFree = LINKSETDATA_MAX - LinksetDataBytesUsed; + BytesUsed += delta; + BytesFree = LINKSETDATA_MAX - BytesUsed; - if (LinksetDataBytesFree < 0) - LinksetDataBytesFree = 0; + if (BytesFree < 0) + BytesFree = 0; } } @@ -374,19 +405,16 @@ public LinksetDataEntry(string value, string password) Value = value; Password = password; } - + public object Clone() { - return new LinksetDataEntry - { - Password = Password, - Value = Value - }; + return JsonSerializer.Deserialize( + JsonSerializer.Serialize(this)); } - public string Password { get; private set; } = string.Empty; + public string Password { get; set; } - public string Value { get; private set; } = string.Empty; + public string Value { get; set; } public bool CheckPassword(string pass) { @@ -400,6 +428,11 @@ public string CheckPasswordAndGetValue(string pass) return CheckPassword(pass) ? Value : string.Empty; } + public bool IsProtected() + { + return !string.IsNullOrEmpty(Password); + } + /// /// Calculate the cost in bytes for this entry. Adds in the passed in key and /// if a password is supplied uses 32 bytes minimum unless the password is longer. @@ -410,30 +443,23 @@ public int GetCost(string key) { int cost = 0; - if (string.IsNullOrWhiteSpace(key)) + if (string.IsNullOrWhiteSpace(key) is false) { - return cost; - } - - cost += Encoding.UTF8.GetBytes(key).Length; + cost += Encoding.UTF8.GetBytes(key).Length; - if (string.IsNullOrWhiteSpace(this.Value) is false) - { - cost += Encoding.UTF8.GetBytes(this.Value).Length; - } + if (string.IsNullOrWhiteSpace(this.Value) is false) + { + cost += Encoding.UTF8.GetBytes(this.Value).Length; + } - if (string.IsNullOrEmpty(this.Password) is false) - { - // For parity, the password adds 32 bytes regardless of the length. See LL caveats - cost += Math.Max(Encoding.UTF8.GetBytes(this.Password).Length, 32); + if (string.IsNullOrEmpty(this.Password) is false) + { + // For parity, the password adds 32 bytes regardless of the length. See LL caveats + cost += Math.Max(Encoding.UTF8.GetBytes(this.Password).Length, 32); + } } return cost; } - - public bool IsProtected() - { - return !string.IsNullOrEmpty(Password); - } } } \ No newline at end of file diff --git a/OpenSim/Region/Framework/Scenes/Scene.cs b/OpenSim/Region/Framework/Scenes/Scene.cs index 9dfb8d97f9b..0e8d3fbd095 100755 --- a/OpenSim/Region/Framework/Scenes/Scene.cs +++ b/OpenSim/Region/Framework/Scenes/Scene.cs @@ -237,8 +237,6 @@ public bool ClampNegativeZ /// public int m_linksetPhysCapacity = 0; - public int m_LinkSetDataLimit = 32 * 1024; - /// /// When placed outside the region's border, do we transfer the objects or /// do we keep simulating them here? @@ -1093,7 +1091,6 @@ public Scene(RegionInfo regInfo, AgentCircuitManager authen, m_update_temp_cleaning = startupConfig.GetInt("UpdateTempCleaningEveryNSeconds", m_update_temp_cleaning); string[] possibleScriptConfigSections = new string[] { "YEngine", "Xengine", "Scripts" }; - m_LinkSetDataLimit = Util.GetConfigVarFromSections(config, "LinksetDataLimit", possibleScriptConfigSections, m_LinkSetDataLimit); } #endregion Region Config diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs index d9e7c1ece3d..eb3ac27c50a 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectGroup.cs @@ -2579,15 +2579,6 @@ public SceneObjectGroup Copy(bool userExposed) public void CopyRootPart(SceneObjectPart part, UUID cAgentID, UUID cGroupID, bool userExposed) { SceneObjectPart newpart = part.Copy(m_scene.AllocateLocalId(), OwnerID, GroupID, 0, userExposed); - // SceneObjectPart newpart = part.Copy(part.LocalId, OwnerID, GroupID, 0, userExposed); - // newpart.LocalId = m_scene.AllocateLocalId(); - - // If the rootpart we're copying has LinksetData do a deep copy of that to the new rootpart. - if (part.LinksetData != null) - { - newpart.LinksetData = (LinksetData)part.LinksetData.Clone(); - } - SetRootPart(newpart); if (userExposed) @@ -3148,9 +3139,8 @@ public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) // If the configured linkset capacity is greater than zero, // and the new linkset would have a prim count higher than this // value, do not link it. - if (m_scene.m_linksetCapacity > 0 && - (PrimCount + objectGroup.PrimCount) > - m_scene.m_linksetCapacity) + if ((m_scene.m_linksetCapacity > 0) && + (PrimCount + objectGroup.PrimCount) > m_scene.m_linksetCapacity) { m_log.DebugFormat( "[SCENE OBJECT GROUP]: Cannot link group with root" + @@ -3194,15 +3184,26 @@ public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) SceneObjectPart linkPart = objectGroup.m_rootPart; // Merge linksetData if there is any - if (linkPart.LinksetData is not null) + if ((linkPart.LinksetData is not null) && (linkPart.LinksetData.Count() > 0)) { - m_rootPart.LinksetData ??= new(); - m_rootPart.LinksetData.MergeLinksetData(linkPart.LinksetData); + if (m_rootPart.LinksetData is null) + { + // If we dont already have linkset data just copy it over + m_rootPart.LinksetData = (LinksetData)linkPart.LinksetData.Clone(); + } + else + { + // Otherwise merge the two LinksetData stores. + m_rootPart.LinksetData.MergeLinksetData(linkPart.LinksetData); + } + + // Clear the data in the old Linkset Part. linkPart.LinksetData.Clear(); } if (m_rootPart.PhysActor != null) m_rootPart.PhysActor.Building = true; + if (linkPart.PhysActor is not null) linkPart.PhysActor.Building = true; @@ -3223,8 +3224,10 @@ public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) // (radams1: Not sure if the multiple setting of OffsetPosition is required. If not, // this code can be reordered to have a more logical flow.) linkPart.setOffsetPosition(linkPart.GroupPosition - AbsolutePosition); + // Assign the new parent to the root of the old group linkPart.ParentID = m_rootPart.LocalId; + // Now that it's a child, it's group position is our root position linkPart.setGroupPosition(AbsolutePosition); @@ -3308,6 +3311,7 @@ public void LinkToGroup(SceneObjectGroup objectGroup, bool insert) part.PhysActor.link(m_rootPart.PhysActor); } } + part.ClearUndoState(); } } diff --git a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs index 880b2901947..3612002adda 100644 --- a/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs +++ b/OpenSim/Region/Framework/Scenes/SceneObjectPart.cs @@ -161,7 +161,7 @@ public bool IsSitTargetSet get { // assume SitTargetOrientation is normalized (as needed elsewhere) - if( !SitTargetPosition.IsZero() || !SitTargetOrientation.IsIdentityOrZero()) + if (!SitTargetPosition.IsZero() || !SitTargetOrientation.IsIdentityOrZero()) return true; return false; } @@ -186,7 +186,7 @@ public bool IsSitTargetSet // This is just a number that needs to change to invalidate prim data caches [XmlIgnore] - public int[] PayPrice = {-2,-2,-2,-2,-2}; + public int[] PayPrice = { -2, -2, -2, -2, -2 }; [XmlIgnore] /// @@ -201,7 +201,7 @@ public bool IsSitTargetSet [XmlIgnore] public LinksetData LinksetData { get; set; } - + //Xantor 20080528 Sound stuff: // Note: This isn't persisted in the database right now, as the fields for that aren't just there yet. // Not a big problem as long as the script that sets it remains in the prim on startup. @@ -243,7 +243,7 @@ public UUID FromUserInventoryItemID public scriptEvents AggregatedScriptEvents; - public Vector3 AttachedPos { get; set; } + public Vector3 AttachedPos { get; set; } // rotation locks on local X,Y and or Z axis bit flags // bits are as in llSetStatus defined in SceneObjectGroup.axisSelect enum @@ -261,7 +261,7 @@ public bool VolumeDetectActive } set { - if(value) + if (value) m_flags |= (PrimFlags)primFlagVolumeDetect; else m_flags &= (PrimFlags)(~primFlagVolumeDetect); @@ -370,15 +370,15 @@ public KeyframeMotion KeyframeMotion #endregion Fields -// ~SceneObjectPart() -// { -// Console.WriteLine( -// "[SCENE OBJECT PART]: Destructor called for {0}, local id {1}, parent {2} {3}", -// Name, LocalId, ParentGroup.Name, ParentGroup.LocalId); -// m_log.DebugFormat( -// "[SCENE OBJECT PART]: Destructor called for {0}, local id {1}, parent {2} {3}", -// Name, LocalId, ParentGroup.Name, ParentGroup.LocalId); -// } + // ~SceneObjectPart() + // { + // Console.WriteLine( + // "[SCENE OBJECT PART]: Destructor called for {0}, local id {1}, parent {2} {3}", + // Name, LocalId, ParentGroup.Name, ParentGroup.LocalId); + // m_log.DebugFormat( + // "[SCENE OBJECT PART]: Destructor called for {0}, local id {1}, parent {2} {3}", + // Name, LocalId, ParentGroup.Name, ParentGroup.LocalId); + // } #region Constructors @@ -579,10 +579,12 @@ public uint InventorySerial /// public TaskInventoryDictionary TaskInventory { - get { + get + { return m_inventory.Items; } - set { + set + { m_inventory.Items = value; } } @@ -608,7 +610,7 @@ public override UUID UUID Inventory?.ResetObjectID(); } } - + // several code still depends on large uncontroled names private osUTF8 osUTF8LargeName; public override string Name @@ -619,7 +621,7 @@ public override string Name } set { - if(string.IsNullOrEmpty(value)) + if (string.IsNullOrEmpty(value)) { osUTF8LargeName = null; osUTF8Name = null; @@ -720,7 +722,7 @@ public Byte[] ParticleSystem { get { - if(m_particleSystemExpire > 0 && Util.GetTimeStamp() > m_particleSystemExpire) + if (m_particleSystemExpire > 0 && Util.GetTimeStamp() > m_particleSystemExpire) { m_particleSystemExpire = -1; m_particleSystem = Array.Empty(); @@ -775,7 +777,7 @@ public Vector3 GroupPosition // If I'm an attachment, my position is reported as the position of who I'm attached to if (ParentGroup.IsAttachment) { - if(ParentGroup.Scene.TryGetScenePresence(ParentGroup.AttachedAvatar, out ScenePresence sp)) + if (ParentGroup.Scene.TryGetScenePresence(ParentGroup.AttachedAvatar, out ScenePresence sp)) return sp.AbsolutePosition; } @@ -827,7 +829,7 @@ public Vector3 OffsetPosition if (ParentGroup != null && !ParentGroup.IsDeleted) { - if((oldpos - m_offsetPosition).LengthSquared() > 1.0f) + if ((oldpos - m_offsetPosition).LengthSquared() > 1.0f) ParentGroup.InvalidBoundsRadius(); PhysicsActor actor = PhysActor; @@ -846,7 +848,7 @@ public Vector3 OffsetPosition { Vector3 offset = (m_offsetPosition - oldpos); av.AbsolutePosition += offset; -// av.SendAvatarDataToAllAgents(); + // av.SendAvatarDataToAllAgents(); av.SendTerseUpdateToAllClients(); } } @@ -895,7 +897,7 @@ public Quaternion RotationOffset set { -// StoreUndoState(); + // StoreUndoState(); m_rotationOffset = value; m_rotationOffset.Normalize(); @@ -952,7 +954,7 @@ public override Vector3 Velocity PhysicsActor actor = PhysActor; if (actor is not null && actor.IsPhysical) - actor.Velocity = m_velocity; + actor.Velocity = m_velocity; } } @@ -1014,7 +1016,7 @@ public Vector3 Acceleration PhysicsActor actor = PhysActor; if ((actor != null) && actor.IsPhysical && ParentGroup.RootPart == this) { - actor.Acceleration = m_acceleration; + actor.Acceleration = m_acceleration; } } } @@ -1022,8 +1024,8 @@ public Vector3 Acceleration public osUTF8 osUTF8Description; public string Description { - get {return osUTF8Description == null ? string.Empty : osUTF8Description.ToString(); } - set { osUTF8Description = string.IsNullOrEmpty(value) ? null : new osUTF8(value, 127);} + get { return osUTF8Description == null ? string.Empty : osUTF8Description.ToString(); } + set { osUTF8Description = string.IsNullOrEmpty(value) ? null : new osUTF8(value, 127); } } /// @@ -1061,13 +1063,13 @@ public int LinkNum get { return m_linkNum; } set { -// if (ParentGroup != null) -// { -// m_log.DebugFormat( -// "[SCENE OBJECT PART]: Setting linknum of {0}@{1} to {2} from {3}", -// Name, AbsolutePosition, value, m_linkNum); -// Util.PrintCallStack(); -// } + // if (ParentGroup != null) + // { + // m_log.DebugFormat( + // "[SCENE OBJECT PART]: Setting linknum of {0}@{1} to {2} from {3}", + // Name, AbsolutePosition, value, m_linkNum); + // Util.PrintCallStack(); + // } m_linkNum = value; } @@ -1103,12 +1105,12 @@ public Vector3 Scale { Vector3 oldscale = m_shape.Scale; m_shape.Scale = value; - if (ParentGroup is not null && ((value - oldscale).LengthSquared() >1.0f)) + if (ParentGroup is not null && ((value - oldscale).LengthSquared() > 1.0f)) ParentGroup.InvalidBoundsRadius(); PhysicsActor actor = PhysActor; if (actor is not null) { - if (ParentGroup.Scene is not null) + if (ParentGroup.Scene is not null) { if (ParentGroup.Scene.PhysicsScene != null) { @@ -1124,15 +1126,15 @@ public Vector3 Scale public float maxSimpleArea() { - float a,b; + float a, b; float sx = m_shape.Scale.X; float sy = m_shape.Scale.Y; float sz = m_shape.Scale.Z; - if( sx > sy) + if (sx > sy) { a = sx; - if(sy > sz) + if (sy > sz) b = sy; else b = sz; @@ -1140,7 +1142,7 @@ public float maxSimpleArea() else { a = sy; - if(sx > sz) + if (sx > sz) b = sx; else b = sz; @@ -1153,7 +1155,7 @@ public float maxSimpleArea() public PrimUpdateFlags GetAndClearUpdateFlag() { - lock(UpdateFlagLock) + lock (UpdateFlagLock) { PrimUpdateFlags ret = UpdateFlag; UpdateFlag = PrimUpdateFlags.None; @@ -1199,15 +1201,15 @@ public bool CreateSelected get { return m_createSelected; } set { -// m_log.DebugFormat("[SOP]: Setting CreateSelected to {0} for {1} {2}", value, Name, UUID); + // m_log.DebugFormat("[SOP]: Setting CreateSelected to {0} for {1} {2}", value, Name, UUID); m_createSelected = value; } } #endregion -//--------------- -#region Public Properties with only Get + //--------------- + #region Public Properties with only Get public override Vector3 AbsolutePosition { @@ -1234,7 +1236,7 @@ public Quaternion SitTargetOrientation set { m_sitTargetOrientation = value; -// m_log.DebugFormat("[SCENE OBJECT PART]: Set sit target orientation {0} for {1} {2}", m_sitTargetOrientation, Name, LocalId); + // m_log.DebugFormat("[SCENE OBJECT PART]: Set sit target orientation {0} for {1} {2}", m_sitTargetOrientation, Name, LocalId); } } @@ -1244,7 +1246,7 @@ public Vector3 SitTargetPosition set { m_sitTargetPosition = value; -// m_log.DebugFormat("[SCENE OBJECT PART]: Set sit target position to {0} for {1} {2}", m_sitTargetPosition, Name, LocalId); + // m_log.DebugFormat("[SCENE OBJECT PART]: Set sit target position to {0} for {1} {2}", m_sitTargetPosition, Name, LocalId); } } @@ -1262,12 +1264,13 @@ public Quaternion SitTargetOrientationLL set { m_sitTargetOrientation = value; } } - public float SitActiveRange { get; set;} - public Vector3 StandOffset { get; set;} + public float SitActiveRange { get; set; } + public Vector3 StandOffset { get; set; } public bool Stopped { - get { + get + { double threshold = 0.02; return (Math.Abs(Velocity.X) < threshold && Math.Abs(Velocity.Y) < threshold && @@ -1337,8 +1340,10 @@ public UUID OwnerID public UUID LastOwnerID { get { return _lastOwnerID; } - set { - _lastOwnerID = value; } + set + { + _lastOwnerID = value; + } } public UUID RezzerID @@ -1418,7 +1423,7 @@ public virtual UUID RegionID else return UUID.Zero; } - set {} // read only + set { } // read only } private UUID _parentUUID = UUID.Zero; @@ -1463,7 +1468,7 @@ public sbyte CollisionSoundType else if (value == 0) m_collisionSound = UUID.Zero; - if(m_collisionSound != old && ParentGroup != null) + if (m_collisionSound != old && ParentGroup != null) ParentGroup.HasGroupChanged = true; } } @@ -1483,7 +1488,7 @@ public UUID CollisionSound else m_collisionSoundType = 1; - if(m_collisionSound != olds && ParentGroup != null) + if (m_collisionSound != olds && ParentGroup != null) ParentGroup.HasGroupChanged = true; } } @@ -1494,11 +1499,11 @@ public float CollisionSoundVolume set { float oldvalue = m_collisionSoundVolume; - if(value >= 0) + if (value >= 0) m_collisionSoundVolume = value; else m_collisionSoundVolume = 0.0f; - if(m_collisionSoundVolume != oldvalue && ParentGroup != null) + if (m_collisionSoundVolume != oldvalue && ParentGroup != null) ParentGroup.HasGroupChanged = true; } } @@ -1601,7 +1606,7 @@ public byte Material if (update) { PhysActor?.SetMaterial((int)value); - if(ParentGroup != null) + if (ParentGroup != null) { ParentGroup.HasGroupChanged = true; ScheduleFullUpdate(); @@ -1638,7 +1643,7 @@ public bool UsesComplexCost get { byte pst = PhysicsShapeType; - if(pst == (byte) PhysShapeType.none || HasMesh()) + if (pst == (byte)PhysShapeType.none || HasMesh()) return true; return false; } @@ -1649,7 +1654,7 @@ public float PhysicsCost { get { - if(PhysicsShapeType == (byte)PhysShapeType.none) + if (PhysicsShapeType == (byte)PhysShapeType.none) return 0; float cost; @@ -1680,7 +1685,7 @@ public float SimulationCost get { // ignoring scripts. Don't like considering them for this - if((m_flags & PrimFlags.Physics) != 0) + if ((m_flags & PrimFlags.Physics) != 0) return 1.0f; return 0.5f; @@ -1691,8 +1696,8 @@ public byte PhysicsShapeType { get { -// if (PhysActor != null) -// m_physicsShapeType = PhysActor.PhysicsShapeType; + // if (PhysActor != null) + // m_physicsShapeType = PhysActor.PhysicsShapeType; return m_physicsShapeType; } set @@ -1717,12 +1722,12 @@ public byte PhysicsShapeType { ParentGroup.Scene.RemovePhysicalPrim(1); RemoveFromPhysics(); -// Stop(); + // Stop(); } } else if (PhysActor == null) { - if(oldv == (byte)PhysShapeType.none) + if (oldv == (byte)PhysShapeType.none) { ApplyPhysics((uint)m_flags, VolumeDetectActive, false); UpdatePhysicsSubscribedEvents(); @@ -1741,7 +1746,7 @@ public float Density // in kg/m^3 get { return m_density; } set { - if (value >=1 && value <= 22587.0) + if (value >= 1 && value <= 22587.0) { m_density = value; @@ -1764,7 +1769,7 @@ public float GravityModifier get { return m_gravitymod; } set { - if( value >= -1 && value <=28.0f) + if (value >= -1 && value <= 28.0f) { m_gravitymod = value; @@ -1839,7 +1844,7 @@ private static uint ApplyMask(uint val, bool set, uint mask) /// public void ClearUpdateSchedule() { - lock(UpdateFlagLock) + lock (UpdateFlagLock) UpdateFlag = PrimUpdateFlags.None; } @@ -1898,7 +1903,7 @@ public void AddFlag(PrimFlags flag) public void AddNewParticleSystem(Primitive.ParticleSystem pSystem, bool expire) { - if(expire && pSystem.MaxAge > 0.0001) + if (expire && pSystem.MaxAge > 0.0001) m_particleSystemExpire = Util.GetTimeStamp() + pSystem.MaxAge + 0.1; else m_particleSystemExpire = -1; @@ -1940,7 +1945,7 @@ public void AdjustSoundGain(double volume) if (volume < 0) volume = 0; - ParentGroup.Scene.ForEachRootClient(delegate(IClientAPI client) + ParentGroup.Scene.ForEachRootClient(delegate (IClientAPI client) { client.SendAttachedSoundGainChange(UUID, (float)volume); }); @@ -2024,7 +2029,7 @@ public void SetAngularVelocity(Vector3 pAngVel, bool localGlobalTF) /// true for the local frame, false for the global frame public void ApplyAngularImpulse(Vector3 impulse, bool localGlobalTF) { - if (ParentGroup == null || ParentGroup.IsDeleted || ParentGroup.inTransit) + if (ParentGroup == null || ParentGroup.IsDeleted || ParentGroup.inTransit) return; if (localGlobalTF) @@ -2068,13 +2073,13 @@ public void ApplyPhysics(uint _ObjectFlags, bool _VolumeDetectActive, bool build if (PhysicsShapeType == (byte)PhysShapeType.none) { - if(ParentID == 0) + if (ParentID == 0) m_physicsShapeType = DefaultPhysicsShapeType(); else return; } - bool isPhysical = (_ObjectFlags & (uint) PrimFlags.Physics) != 0; + bool isPhysical = (_ObjectFlags & (uint)PrimFlags.Physics) != 0; bool isPhantom = (_ObjectFlags & (uint)PrimFlags.Phantom) != 0; if (_VolumeDetectActive) @@ -2158,7 +2163,7 @@ public SceneObjectPart Copy(uint plocalID, UUID AgentID, UUID GroupID, int linkN dupe.LocalId = plocalID; // This may be wrong... it might have to be applied in SceneObjectGroup to the object that's being duplicated. - if(OwnerID.NotEqual(GroupID)) + if (OwnerID.NotEqual(GroupID)) dupe.LastOwnerID = OwnerID; else dupe.LastOwnerID = LastOwnerID; // redundant ? @@ -2169,11 +2174,12 @@ public SceneObjectPart Copy(uint plocalID, UUID AgentID, UUID GroupID, int linkN byte[] extraP = new byte[oldextrap.Length]; Array.Copy(oldextrap, extraP, extraP.Length); dupe.Shape.ExtraParams = extraP; - if(Shape.RenderMaterials is not null && Shape.RenderMaterials.overrides is not null && + + if (Shape.RenderMaterials is not null && Shape.RenderMaterials.overrides is not null && Shape.RenderMaterials.overrides.Length > 0) - { + { dupe.Shape.RenderMaterials.overrides = new Primitive.RenderMaterials.RenderMaterialOverrideEntry[Shape.RenderMaterials.overrides.Length]; - Shape.RenderMaterials.overrides.CopyTo(dupe.Shape.RenderMaterials.overrides,0); + Shape.RenderMaterials.overrides.CopyTo(dupe.Shape.RenderMaterials.overrides, 0); } dupe.m_sittingAvatars = new HashSet(); @@ -2182,7 +2188,7 @@ public SceneObjectPart Copy(uint plocalID, UUID AgentID, UUID GroupID, int linkN dupe.KeyframeMotion = null; dupe.PayPrice = (int[])PayPrice.Clone(); - if(DynAttrs != null) + if (DynAttrs != null) dupe.DynAttrs.CopyFrom(DynAttrs); if (userExposed) @@ -2196,16 +2202,14 @@ public SceneObjectPart Copy(uint plocalID, UUID AgentID, UUID GroupID, int linkN dupe.PseudoCRC = (int)(DateTime.UtcNow.Ticks); - if (LinksetData != null) + dupe.LinksetData = null; + if (LinksetData is not null) { dupe.LinksetData = (LinksetData)LinksetData.Clone(); - //dupe.DeserializeLinksetData(SerializeLinksetData()); // MCD XXX } ParentGroup.Scene.EventManager.TriggerOnSceneObjectPartCopy(dupe, this, userExposed); -// m_log.DebugFormat("[SCENE OBJECT PART]: Clone of {0} {1} finished", Name, UUID); - return dupe; } @@ -2350,7 +2354,7 @@ public bool GetStatusSandbox() public int GetAxisRotation(int axis) { - if (!ParentGroup.IsDeleted) + if (!ParentGroup.IsDeleted) return ParentGroup.GetAxisRotation(axis); return 0; @@ -2359,7 +2363,7 @@ public int GetAxisRotation(int axis) public uint GetEffectiveObjectFlags() { uint eff = (uint)(m_flags | m_localFlags); - if(m_inventory == null || m_inventory.Count == 0) + if (m_inventory == null || m_inventory.Count == 0) eff |= (uint)PrimFlags.InventoryEmpty; return eff; } @@ -2367,9 +2371,9 @@ public uint GetEffectiveObjectFlags() // some of this lines need be moved to other place later // effective permitions considering only this part inventory contents perms - public uint AggregatedInnerOwnerPerms {get; private set; } - public uint AggregatedInnerGroupPerms {get; private set; } - public uint AggregatedInnerEveryonePerms {get; private set; } + public uint AggregatedInnerOwnerPerms { get; private set; } + public uint AggregatedInnerGroupPerms { get; private set; } + public uint AggregatedInnerEveryonePerms { get; private set; } private readonly object InnerPermsLock = new object(); public void AggregateInnerPerms() @@ -2382,10 +2386,10 @@ public void AggregateInnerPerms() uint group = mask; uint everyone = mask; - lock(InnerPermsLock) // do we really need this? + lock (InnerPermsLock) // do we really need this? { Inventory?.AggregateInnerPerms(ref owner, ref group, ref everyone); - + AggregatedInnerOwnerPerms = owner & mask; AggregatedInnerGroupPerms = group & mask; AggregatedInnerEveryonePerms = everyone & mask; @@ -2404,10 +2408,10 @@ public void AggregatedInnerPermsForGroup() uint group = mask; uint everyone = mask; - lock(InnerPermsLock) // do we really need this? + lock (InnerPermsLock) // do we really need this? { Inventory?.AggregateInnerPerms(ref owner, ref group, ref everyone); - + AggregatedInnerOwnerPerms = owner & mask; AggregatedInnerGroupPerms = group & mask; AggregatedInnerEveryonePerms = everyone & mask; @@ -2498,7 +2502,7 @@ public virtual void OnGrab(Vector3 offsetPos, IClientAPI remoteClient) public void SetCollisionFilter(bool access, string name, string id) { - if(string.IsNullOrEmpty(id)) + if (string.IsNullOrEmpty(id)) { if (string.IsNullOrEmpty(name)) { @@ -2588,9 +2592,9 @@ private DetectedObject CreateDetObject(ScenePresence av) groupUUID = av.ControllingClient.ActiveGroupId, linkNumber = LinkNum }; - if(av.IsSatOnObject) + if (av.IsSatOnObject) detobj.colliderType |= 0x4; //passive - else if(!detobj.velVector.IsZero()) + else if (!detobj.velVector.IsZero()) detobj.colliderType |= 0x2; //active return detobj; } @@ -2683,11 +2687,11 @@ private void SendLandCollisionEvent(scriptEvents ev, ScriptCollidingNotification if ((ScriptEvents & ev) != 0) { notify(LocalId, LandCollidingMessage); - if(!PassCollisions) + if (!PassCollisions) return; } SceneObjectPart root = ParentGroup.RootPart; - if(LocalId != root.LocalId && (root.ScriptEvents & ev) != 0) + if (LocalId != root.LocalId && (root.ScriptEvents & ev) != 0) notify(ParentGroup.RootPart.LocalId, LandCollidingMessage); } @@ -2738,7 +2742,7 @@ public void PhysicsCollision(EventArgs e) foreach (uint id in collissionswith.Keys) { - if(id == 0) + if (id == 0) { thisHitLand = true; startLand = !m_lastLandCollide; @@ -2786,7 +2790,7 @@ public void PhysicsCollision(EventArgs e) { foreach (uint id in collissionswith.Keys) { - if(id == 0) + if (id == 0) { thisHitLand = true; startLand = !m_lastLandCollide; @@ -2814,7 +2818,7 @@ public void PhysicsCollision(EventArgs e) if ((combinedEvents & scriptEvents.collision_start) != 0) SendCollisionEvent(scriptEvents.collision_start, startedColliders, eventManager.TriggerScriptCollidingStart); if ((combinedEvents & scriptEvents.collision_end) != 0) - SendCollisionEvent(scriptEvents.collision_end , endedColliders , eventManager.TriggerScriptCollidingEnd); + SendCollisionEvent(scriptEvents.collision_end, endedColliders, eventManager.TriggerScriptCollidingEnd); } if (!VolumeDetectActive) @@ -2831,7 +2835,7 @@ public void PhysicsCollision(EventArgs e) if ((combinedEvents & scriptEvents.land_collision) != 0) SendLandCollisionEvent(scriptEvents.land_collision, eventManager.TriggerScriptLandColliding); } - else if(m_lastLandCollide && (combinedEvents & scriptEvents.land_collision_end) != 0) + else if (m_lastLandCollide && (combinedEvents & scriptEvents.land_collision_end) != 0) SendLandCollisionEvent(scriptEvents.land_collision_end, eventManager.TriggerScriptLandCollidingEnd); } } @@ -2902,8 +2906,8 @@ public void PhysicsRequestingTerseUpdate() public void RemFlag(PrimFlags flag) { - m_flags &= ~flag; - } + m_flags &= ~flag; + } public void RemoveScriptEvents(UUID scriptid) { @@ -2911,8 +2915,8 @@ public void RemoveScriptEvents(UUID scriptid) { if (m_scriptEvents.TryGetValue(scriptid, out scriptEvents ev)) { - if((ev & (scriptEvents.anyTarget)) != 0 && ParentGroup != null) - ParentGroup.RemoveScriptTargets(scriptid); + if ((ev & (scriptEvents.anyTarget)) != 0 && ParentGroup != null) + ParentGroup.RemoveScriptTargets(scriptid); if ((AggregatedScriptEvents & scriptEvents.email) != 0) { IEmailModule scriptEmail = ParentGroup.Scene.RequestModuleInterface(); @@ -3005,15 +3009,15 @@ public void Resize(Vector3 scale) float minsize = ParentGroup.Scene.m_minNonphys; float maxsize = ParentGroup.Scene.m_maxNonphys; if (pa != null && pa.IsPhysical) - { + { minsize = ParentGroup.Scene.m_minPhys; maxsize = ParentGroup.Scene.m_maxPhys; - } + } scale.X = Utils.Clamp(scale.X, minsize, maxsize); scale.Y = Utils.Clamp(scale.Y, minsize, maxsize); scale.Z = Utils.Clamp(scale.Z, minsize, maxsize); } -// m_log.DebugFormat("[SCENE OBJECT PART]: Resizing {0} {1} to {2}", Name, LocalId, scale); + // m_log.DebugFormat("[SCENE OBJECT PART]: Resizing {0} {1} to {2}", Name, LocalId, scale); Scale = scale; @@ -3023,15 +3027,15 @@ public void Resize(Vector3 scale) public void RotLookAt(Quaternion target, float strength, float damping) { - if(ParentGroup.IsDeleted) + if (ParentGroup.IsDeleted) return; // for now we only handle physics case - if(!ParentGroup.UsesPhysics || ParentGroup.IsAttachment) + if (!ParentGroup.UsesPhysics || ParentGroup.IsAttachment) return; // physical is SOG - if(ParentGroup.RootPart != this) + if (ParentGroup.RootPart != this) { ParentGroup.RotLookAt(target, strength, damping); return; @@ -3043,7 +3047,7 @@ public void RotLookAt(Quaternion target, float strength, float damping) if (APIDStrength <= 0) { - m_log.WarnFormat("[SceneObjectPart] Invalid rotation strength {0}",APIDStrength); + m_log.WarnFormat("[SceneObjectPart] Invalid rotation strength {0}", APIDStrength); return; } @@ -3055,30 +3059,30 @@ public void RotLookAt(Quaternion target, float strength, float damping) public void StartLookAt(Quaternion target, float strength, float damping) { - if(ParentGroup.IsDeleted) + if (ParentGroup.IsDeleted) return; // non physical is done on LSL - if(ParentGroup.IsAttachment || !ParentGroup.UsesPhysics) + if (ParentGroup.IsAttachment || !ParentGroup.UsesPhysics) return; // physical is SOG - if(ParentGroup.RootPart != this) + if (ParentGroup.RootPart != this) ParentGroup.RotLookAt(target, strength, damping); else - RotLookAt(target,strength,damping); + RotLookAt(target, strength, damping); } public void StopLookAt() { - if(ParentGroup.IsDeleted) + if (ParentGroup.IsDeleted) return; - if(ParentGroup.RootPart != this && ParentGroup.UsesPhysics) - ParentGroup.StopLookAt(); + if (ParentGroup.RootPart != this && ParentGroup.UsesPhysics) + ParentGroup.StopLookAt(); // just in case do this always - if(APIDActive) + if (APIDActive) AngularVelocity = Vector3.Zero; APIDActive = false; @@ -3098,7 +3102,7 @@ public void ScheduleFullUpdate() ParentGroup.QueueForUpdateCheck(); // just in case - lock(UpdateFlagLock) + lock (UpdateFlagLock) UpdateFlag |= PrimUpdateFlags.FullUpdate; ParentGroup.Scene.EventManager.TriggerSceneObjectPartUpdated(this, true); @@ -3138,7 +3142,7 @@ public void ScheduleTerseUpdate() ++PseudoCRC; ParentGroup.HasGroupChanged = true; - if(ParentGroup.Scene.GetNumberOfClients() == 0) + if (ParentGroup.Scene.GetNumberOfClients() == 0) return; ParentGroup.QueueForUpdateCheck(); @@ -3257,7 +3261,7 @@ public void SendFullUpdateToAllClientsNoAttachment() UpdateFlag &= ~PrimUpdateFlags.FullUpdate; } - ParentGroup.Scene.ForEachScenePresence(delegate(ScenePresence avatar) + ParentGroup.Scene.ForEachScenePresence(delegate (ScenePresence avatar) { SendUpdateToClient(avatar.ControllingClient, PrimUpdateFlags.FullUpdate); }); @@ -3288,7 +3292,7 @@ public void SendFullUpdateToAllClients() } else { - ParentGroup.Scene.ForEachScenePresence(delegate(ScenePresence avatar) + ParentGroup.Scene.ForEachScenePresence(delegate (ScenePresence avatar) { SendUpdateToClient(avatar.ControllingClient, PrimUpdateFlags.FullUpdate); }); @@ -3327,24 +3331,24 @@ private static Vector3 ClampVectorForTerseUpdate(Vector3 v, float max) /// Tell all the prims which have had updates scheduled /// public void SendScheduledUpdates(double now) - { + { PrimUpdateFlags current; - lock (UpdateFlagLock) + lock (UpdateFlagLock) { current = UpdateFlag; if (current == PrimUpdateFlags.None) return; - if(current == PrimUpdateFlags.TerseUpdate) + if (current == PrimUpdateFlags.TerseUpdate) { - while(true) // just to avoid ugly goto + while (true) // just to avoid ugly goto { double elapsed = now - m_lastUpdateSentTime; if (elapsed > TIME_MS_TOLERANCE) break; - if ( !Acceleration.ApproxEquals(m_lastAcceleration, VELOCITY_TOLERANCE)) + if (!Acceleration.ApproxEquals(m_lastAcceleration, VELOCITY_TOLERANCE)) break; Vector3 curvel = ClampVectorForTerseUpdate(Velocity, 128f); @@ -3356,11 +3360,11 @@ public void SendScheduledUpdates(double now) { if (!AbsolutePosition.ApproxEquals(m_lastPosition, POSITION_TOLERANCE)) break; - if ( Math.Abs(m_lastVelocity.X) > 1e-4 || + if (Math.Abs(m_lastVelocity.X) > 1e-4 || Math.Abs(m_lastVelocity.Y) > 1e-4 || Math.Abs(m_lastVelocity.Z) > 1e-4 ) - break; + break; } Vector3 angvel = ClampVectorForTerseUpdate(AngularVelocity, 64f); @@ -3368,7 +3372,7 @@ public void SendScheduledUpdates(double now) if (!angvel.ApproxEquals(tmp, ANGVELOCITY_TOLERANCE)) break; - if ( Math.Abs(AngularVelocity.X) < 1e-4 && + if (Math.Abs(AngularVelocity.X) < 1e-4 && Math.Abs(AngularVelocity.Y) < 1e-4 && Math.Abs(AngularVelocity.Z) < 1e-4 && !RotationOffset.ApproxEquals(m_lastRotation, ROTATION_TOLERANCE) @@ -3379,7 +3383,7 @@ public void SendScheduledUpdates(double now) } } - if((current & PrimUpdateFlags.TerseUpdate) != 0) + if ((current & PrimUpdateFlags.TerseUpdate) != 0) { m_lastPosition = AbsolutePosition; m_lastRotation = RotationOffset; @@ -3392,7 +3396,7 @@ public void SendScheduledUpdates(double now) UpdateFlag = PrimUpdateFlags.None; } - ParentGroup.Scene.ForEachScenePresence(delegate(ScenePresence avatar) + ParentGroup.Scene.ForEachScenePresence(delegate (ScenePresence avatar) { SendUpdateToClient(avatar.ControllingClient, current); }); @@ -3406,7 +3410,7 @@ public void SendTerseUpdateToAllClientsInternal() if (ParentGroup == null || ParentGroup.Scene == null) return; - lock(UpdateFlagLock) + lock (UpdateFlagLock) { UpdateFlag &= ~PrimUpdateFlags.TerseUpdate; @@ -3419,7 +3423,7 @@ public void SendTerseUpdateToAllClientsInternal() m_lastUpdateSentTime = Util.GetTimeStampMS(); } - ParentGroup.Scene.ForEachClient(delegate(IClientAPI client) + ParentGroup.Scene.ForEachClient(delegate (IClientAPI client) { SendTerseUpdateToClient(client); }); @@ -3430,7 +3434,7 @@ public void SendTerseUpdateToAllClients() if (ParentGroup == null || ParentGroup.Scene == null) return; - lock(UpdateFlagLock) + lock (UpdateFlagLock) { UpdateFlag &= ~PrimUpdateFlags.TerseUpdate; @@ -3450,7 +3454,7 @@ public void SendTerseUpdateToAllClients() } else { - ParentGroup.Scene.ForEachClient(delegate(IClientAPI client) + ParentGroup.Scene.ForEachClient(delegate (IClientAPI client) { SendTerseUpdateToClient(client); }); @@ -3530,21 +3534,21 @@ public int VehicleType public void SetVehicleType(int type) { - m_vehicleParams = null; + m_vehicleParams = null; - if (type == (int)Vehicle.TYPE_NONE) - { - if (_parentID ==0 && PhysActor != null) - PhysActor.VehicleType = (int)Vehicle.TYPE_NONE; - return; - } - m_vehicleParams = new SOPVehicle(); - m_vehicleParams.ProcessTypeChange((Vehicle)type); - { - if (_parentID ==0 && PhysActor != null) - PhysActor.VehicleType = type; - return; - } + if (type == (int)Vehicle.TYPE_NONE) + { + if (_parentID == 0 && PhysActor != null) + PhysActor.VehicleType = (int)Vehicle.TYPE_NONE; + return; + } + m_vehicleParams = new SOPVehicle(); + m_vehicleParams.ProcessTypeChange((Vehicle)type); + { + if (_parentID == 0 && PhysActor != null) + PhysActor.VehicleType = type; + return; + } } public void SetVehicleFlags(int param, bool remove) @@ -3605,7 +3609,7 @@ public void SetVehicleRotationParam(int param, Quaternion rotation) /// /// /// - public void SetFaceColorAlpha(int face, Vector3 color, float ?alpha) + public void SetFaceColorAlpha(int face, Vector3 color, float? alpha) { Vector3 clippedColor = Vector3.Clamp(color, 0.0f, 1.0f); float clippedAlpha = alpha.HasValue ? @@ -3671,7 +3675,7 @@ public int GetNumberOfSides() int ret = 0; int cut; - if(Shape.SculptEntry) + if (Shape.SculptEntry) { if (Shape.SculptType != (byte)SculptType.Mesh) return 1; // sculp @@ -3714,17 +3718,17 @@ public int GetNumberOfSides() if (Shape.ProfileBegin > 0) { cut = (Shape.ProfileBegin); - if(cut >= 16667 ) + if (cut >= 16667) ret--; - if(cut >= 33333 ) + if (cut >= 33333) ret--; } if (Shape.ProfileEnd > 0) { cut = (Shape.ProfileEnd); - if(cut >= 16667 ) + if (cut >= 16667) ret--; - if(cut >= 33333 ) + if (cut >= 33333) ret--; } ret += 2; // both cut faces @@ -3786,17 +3790,17 @@ public int GetNumberOfSides() if (Shape.ProfileBegin > 0) { cut = Shape.ProfileBegin; - if(cut >= 16667 ) + if (cut >= 16667) ret--; - if(cut >= 33333 ) + if (cut >= 33333) ret--; } if (Shape.ProfileEnd > 0) { cut = Shape.ProfileEnd; - if(cut >= 16667 ) + if (cut >= 16667) ret--; - if(cut >= 33333 ) + if (cut >= 33333) ret--; } ret += 2; @@ -3863,7 +3867,7 @@ public void SetGroup(UUID groupID, IClientAPI client) GroupID = groupID; // if (client != null) // SendPropertiesToClient(client); - lock(UpdateFlagLock) + lock (UpdateFlagLock) UpdateFlag |= PrimUpdateFlags.FullUpdate; } @@ -3904,7 +3908,7 @@ public void SetScriptEvents(UUID scriptid, ulong events) if (ev == (scriptEvents)events) return; } - m_scriptEvents[scriptid] = (scriptEvents) events; + m_scriptEvents[scriptid] = (scriptEvents)events; } aggregateScriptEvents(); } @@ -3916,7 +3920,7 @@ public void SetScriptEvents(UUID scriptid, ulong events) public void SetText(string text) { osUTF8 old = osUTF8Text; - if(string.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) { osUTF8Text = null; if (old != null && ParentGroup != null) @@ -3946,12 +3950,12 @@ public void SetText(string text, Vector3 color, double alpha) { Color oldcolor = Color; - Color = Color.FromArgb((int) (alpha * 0xff), - (int) (color.X * 0xff), - (int) (color.Y * 0xff), - (int) (color.Z * 0xff)); + Color = Color.FromArgb((int)(alpha * 0xff), + (int)(color.X * 0xff), + (int)(color.Y * 0xff), + (int)(color.Z * 0xff)); osUTF8 old = osUTF8Text; - if(string.IsNullOrEmpty(text)) + if (string.IsNullOrEmpty(text)) { osUTF8Text = null; if (ParentGroup != null && (oldcolor != Color || old != null)) @@ -4121,7 +4125,7 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo tScale = new Vector3(AXscale.X, -AXscale.Y, AXscale.Z); rScale = tScale * AXrot; vertexes[0] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); - // vertexes[0].X = pos.X + vertexes[0].X; + // vertexes[0].X = pos.X + vertexes[0].X; //vertexes[0].Y = pos.Y + vertexes[0].Y; //vertexes[0].Z = pos.Z + vertexes[0].Z; @@ -4133,8 +4137,8 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo rScale = tScale * AXrot; vertexes[1] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); - // vertexes[1].X = pos.X + vertexes[1].X; - // vertexes[1].Y = pos.Y + vertexes[1].Y; + // vertexes[1].X = pos.X + vertexes[1].X; + // vertexes[1].Y = pos.Y + vertexes[1].Y; //vertexes[1].Z = pos.Z + vertexes[1].Z; FaceB[0] = vertexes[1]; @@ -4159,8 +4163,8 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo vertexes[3] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); //vertexes[3].X = pos.X + vertexes[3].X; - // vertexes[3].Y = pos.Y + vertexes[3].Y; - // vertexes[3].Z = pos.Z + vertexes[3].Z; + // vertexes[3].Y = pos.Y + vertexes[3].Y; + // vertexes[3].Z = pos.Z + vertexes[3].Z; FaceD[0] = vertexes[3]; FaceC[1] = vertexes[3]; @@ -4170,9 +4174,9 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo rScale = tScale * AXrot; vertexes[4] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); - // vertexes[4].X = pos.X + vertexes[4].X; - // vertexes[4].Y = pos.Y + vertexes[4].Y; - // vertexes[4].Z = pos.Z + vertexes[4].Z; + // vertexes[4].X = pos.X + vertexes[4].X; + // vertexes[4].Y = pos.Y + vertexes[4].Y; + // vertexes[4].Z = pos.Z + vertexes[4].Z; FaceB[1] = vertexes[4]; FaceA[2] = vertexes[4]; @@ -4182,9 +4186,9 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo rScale = tScale * AXrot; vertexes[5] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); - // vertexes[5].X = pos.X + vertexes[5].X; - // vertexes[5].Y = pos.Y + vertexes[5].Y; - // vertexes[5].Z = pos.Z + vertexes[5].Z; + // vertexes[5].X = pos.X + vertexes[5].X; + // vertexes[5].Y = pos.Y + vertexes[5].Y; + // vertexes[5].Z = pos.Z + vertexes[5].Z; FaceD[1] = vertexes[5]; FaceC[2] = vertexes[5]; @@ -4194,9 +4198,9 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo rScale = tScale * AXrot; vertexes[6] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); - // vertexes[6].X = pos.X + vertexes[6].X; - // vertexes[6].Y = pos.Y + vertexes[6].Y; - // vertexes[6].Z = pos.Z + vertexes[6].Z; + // vertexes[6].X = pos.X + vertexes[6].X; + // vertexes[6].Y = pos.Y + vertexes[6].Y; + // vertexes[6].Z = pos.Z + vertexes[6].Z; FaceB[2] = vertexes[6]; FaceA[3] = vertexes[6]; @@ -4206,9 +4210,9 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo rScale = tScale * AXrot; vertexes[7] = (new Vector3((pos.X + rScale.X), (pos.Y + rScale.Y), (pos.Z + rScale.Z))); - // vertexes[7].X = pos.X + vertexes[7].X; - // vertexes[7].Y = pos.Y + vertexes[7].Y; - // vertexes[7].Z = pos.Z + vertexes[7].Z; + // vertexes[7].X = pos.X + vertexes[7].X; + // vertexes[7].Y = pos.Y + vertexes[7].Y; + // vertexes[7].Z = pos.Z + vertexes[7].Z; FaceD[2] = vertexes[7]; FaceC[3] = vertexes[7]; @@ -4250,49 +4254,49 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo //for (int i=0;i<6;i++) //{ - //s = iray.Direction.Dot(normals[i]); - //d = normals[i].Dot(FaceB[i]); + //s = iray.Direction.Dot(normals[i]); + //d = normals[i].Dot(FaceB[i]); - //if (s == 0) - //{ - //if (iray.Origin.Dot(normals[i]) > d) - //{ - //return result; - //} - // else - //{ - //continue; - //} - //} - //a = (d - iray.Origin.Dot(normals[i])) / s; - //if (iray.Direction.Dot(normals[i]) < 0) - //{ - //if (a > fmax) - //{ - //if (a > fmin) - //{ - //return result; - //} - //fmax = a; - //} + //if (s == 0) + //{ + //if (iray.Origin.Dot(normals[i]) > d) + //{ + //return result; + //} + // else + //{ + //continue; + //} + //} + //a = (d - iray.Origin.Dot(normals[i])) / s; + //if (iray.Direction.Dot(normals[i]) < 0) + //{ + //if (a > fmax) + //{ + //if (a > fmin) + //{ + //return result; + //} + //fmax = a; + //} - //} - //else - //{ - //if (a < fmin) - //{ - //if (a < 0 || a < fmax) - //{ - //return result; - //} - //fmin = a; - //} - //} + //} + //else + //{ + //if (a < fmin) + //{ + //if (a < 0 || a < fmax) + //{ + //return result; + //} + //fmin = a; + //} + //} //} //if (fmax > 0) // a= fmax; //else - // a=fmin; + // a=fmin; //q = iray.Origin + a * iray.Direction; #endregion @@ -4329,7 +4333,7 @@ public EntityIntersection TestIntersectionOBB(Ray iray, Quaternion parentrot, bo //} //else //{ - q = iray.Origin + iray.Direction * a; + q = iray.Origin + iray.Direction * a; //} float distance2 = Vector3.Distance(q, AXpos); @@ -4542,16 +4546,16 @@ public void UpdateExtraPhysics(ExtraPhysicsData physdata) if (PhysicsShapeType != newtype) PhysicsShapeType = newtype; - if(Density != physdata.Density) + if (Density != physdata.Density) Density = physdata.Density; - if(GravityModifier != physdata.GravitationModifier) + if (GravityModifier != physdata.GravitationModifier) GravityModifier = physdata.GravitationModifier; - if(Friction != physdata.Friction) + if (Friction != physdata.Friction) Friction = physdata.Friction; - if(Restitution != physdata.Bounce) + if (Restitution != physdata.Bounce) Restitution = physdata.Bounce; } /// @@ -4579,7 +4583,7 @@ public void UpdatePrimFlags(bool UsePhysics, bool SetTemporary, bool SetPhantom, // volume detector implies phantom we need to decouple this mess if (SetVD) SetPhantom = true; - else if(wasVD) + else if (wasVD) SetPhantom = false; if (UsePhysics) @@ -4605,13 +4609,13 @@ public void UpdatePrimFlags(bool UsePhysics, bool SetTemporary, bool SetPhantom, if (pa != null && building && pa.Building != building) pa.Building = building; - if ((SetPhantom && !UsePhysics && !SetVD) || ParentGroup.IsAttachment || PhysicsShapeType == (byte)PhysShapeType.none + if ((SetPhantom && !UsePhysics && !SetVD) || ParentGroup.IsAttachment || PhysicsShapeType == (byte)PhysShapeType.none || (Shape.PathCurve == (byte)Extrusion.Flexible)) { Stop(); if (pa != null) { - if(wasUsingPhysics) + if (wasUsingPhysics) ParentGroup.Scene.RemovePhysicalPrim(1); RemoveFromPhysics(); } @@ -4637,9 +4641,9 @@ public void UpdatePrimFlags(bool UsePhysics, bool SetTemporary, bool SetPhantom, DoPhysicsPropertyUpdate(UsePhysics, false); // Update physical status. - if(UsePhysics && !SetPhantom && m_localId == ParentGroup.RootPart.LocalId && + if (UsePhysics && !SetPhantom && m_localId == ParentGroup.RootPart.LocalId && m_vehicleParams != null && m_vehicleParams.CameraDecoupled) - AddFlag(PrimFlags.CameraDecoupled); + AddFlag(PrimFlags.CameraDecoupled); else RemFlag(PrimFlags.CameraDecoupled); @@ -4659,7 +4663,7 @@ public void UpdatePrimFlags(bool UsePhysics, bool SetTemporary, bool SetPhantom, ScheduleFullUpdate(); } -// m_log.DebugFormat("[SCENE OBJECT PART]: Updated PrimFlags on {0} {1} to {2}", Name, LocalId, Flags); + // m_log.DebugFormat("[SCENE OBJECT PART]: Updated PrimFlags on {0} {1} to {2}", Name, LocalId, Flags); } /// @@ -4709,23 +4713,23 @@ private void AddToPhysics(bool isPhysical, bool isPhantom, bool building, bool a pa.Restitution = Restitution; pa.Buoyancy = Buoyancy; - if(LocalId == ParentGroup.RootPart.LocalId) + if (LocalId == ParentGroup.RootPart.LocalId) { pa.LockAngularMotion(RotationAxisLocks); } if (VolumeDetectActive) // change if not the default only pa.SetVolumeDetect(1); - + bool isroot = (m_localId == ParentGroup.RootPart.LocalId); - if(isroot && m_physicsInertia != null) + if (isroot && m_physicsInertia != null) pa.SetInertiaData(m_physicsInertia); - if (isroot && m_vehicleParams != null ) + if (isroot && m_vehicleParams != null) { m_vehicleParams.SetVehicle(pa); - if(isPhysical && !isPhantom && m_vehicleParams.CameraDecoupled) + if (isPhysical && !isPhantom && m_vehicleParams.CameraDecoupled) AddFlag(PrimFlags.CameraDecoupled); else RemFlag(PrimFlags.CameraDecoupled); @@ -4759,7 +4763,7 @@ private void AddToPhysics(bool isPhysical, bool isPhantom, bool building, bool a } if (applyDynamics && LocalId == ParentGroup.RootPart.LocalId) - // do independent of isphysical so parameters get setted (at least some) + // do independent of isphysical so parameters get setted (at least some) { Velocity = velocity; AngularVelocity = rotationalVelocity; @@ -4993,7 +4997,7 @@ public void UpdateTextureEntry(byte[] serializedTextureEntry) if (changeFlags == 0) return; // we do need better compacter do just the trivial case - if(nsides == 1 && newTex.FaceTextures[0] != null) + if (nsides == 1 && newTex.FaceTextures[0] != null) { newTex.DefaultTexture = newTex.GetFace(0); newTex.FaceTextures[0] = null; @@ -5012,11 +5016,11 @@ public void UpdateTextureEntry(Primitive.TextureEntry newTex) { TextureAttributes dirty = newTex.GetDirtyFlags(32, true); dirty &= ~TextureAttributes.MaterialID; - if(dirty == TextureAttributes.None) + if (dirty == TextureAttributes.None) return; Changed changeFlags = 0; - if((dirty & TextureAttributes.RGBA) != 0) + if ((dirty & TextureAttributes.RGBA) != 0) { changeFlags = Changed.COLOR; dirty &= ~TextureAttributes.RGBA; @@ -5086,19 +5090,19 @@ public void aggregateScriptEvents() } uint objectflagupdate = 0; - if ((AggregatedScriptEvents & scriptEvents.anytouch) != 0 ) + if ((AggregatedScriptEvents & scriptEvents.anytouch) != 0) { - objectflagupdate |= (uint) PrimFlags.Touch; + objectflagupdate |= (uint)PrimFlags.Touch; } if ((AggregatedScriptEvents & scriptEvents.money) != 0) { - objectflagupdate |= (uint) PrimFlags.Money; + objectflagupdate |= (uint)PrimFlags.Money; } if (AllowedDrop) { - objectflagupdate |= (uint) PrimFlags.AllowInventoryDrop; + objectflagupdate |= (uint)PrimFlags.AllowInventoryDrop; } m_localFlags = (PrimFlags)objectflagupdate; @@ -5115,8 +5119,8 @@ public void aggregateScriptEvents() } else { -// m_log.DebugFormat( -// "[SCENE OBJECT PART]: Scheduling part {0} {1} for full update in aggregateScriptEvents()", Name, LocalId); + // m_log.DebugFormat( + // "[SCENE OBJECT PART]: Scheduling part {0} {1} for full update in aggregateScriptEvents()", Name, LocalId); UpdatePhysicsSubscribedEvents(); ScheduleFullUpdate(); } @@ -5171,10 +5175,10 @@ public void SendUpdateToClient(IClientAPI remoteClient, PrimUpdateFlags update) return; } - if (ParentGroup.IsAttachment && + if (ParentGroup.IsAttachment && (ParentGroup.RootPart != this || ParentGroup.AttachedAvatar != remoteClient.AgentId && ParentGroup.HasPrivateAttachmentPoint)) return; - + remoteClient.SendEntityUpdate(this, update); ParentGroup.Scene.StatsReporter.AddObjectUpdates(1); @@ -5244,7 +5248,7 @@ public void ApplyPermissionsOnRez(InventoryItemBase item, bool userInventory, Sc if (OwnerID.NotEqual(item.Owner)) { - if(OwnerID.NotEqual(GroupID)) + if (OwnerID.NotEqual(GroupID)) LastOwnerID = OwnerID; OwnerID = item.Owner; Inventory.ChangeInventoryOwner(item.Owner); @@ -5356,7 +5360,7 @@ protected internal bool AddSittingAvatar(ScenePresence sp) if (m_sittingAvatars.Add(sp)) { - if(!ParentGroup.m_sittingAvatars.Contains(sp)) + if (!ParentGroup.m_sittingAvatars.Contains(sp)) ParentGroup.m_sittingAvatars.Add(sp); return true; @@ -5389,7 +5393,7 @@ protected internal bool RemoveSittingAvatar(ScenePresence sp) if (m_sittingAvatars.Count == 0) m_sittingAvatars = null; - if(ParentGroup.m_sittingAvatars.Remove(sp)) + if (ParentGroup.m_sittingAvatars.Remove(sp)) ParentGroup.InvalidatePartsLinkMaps(false); return true; } @@ -5441,24 +5445,24 @@ public void Stop() // handle osVolumeDetect public void ScriptSetVolumeDetect(bool makeVolumeDetect) { - if(ParentGroup.IsDeleted) + if (ParentGroup.IsDeleted) return; - if(_parentID == 0) + if (_parentID == 0) { // if root prim do it is like llVolumeDetect ParentGroup.ScriptSetVolumeDetect(makeVolumeDetect); return; } - if(ParentGroup.IsVolumeDetect) + if (ParentGroup.IsVolumeDetect) return; // entire linkset is phantom already bool wasUsingPhysics = ParentGroup.UsesPhysics; bool wasTemporary = ParentGroup.IsTemporary; bool wasPhantom = ParentGroup.IsPhantom; - if(PhysActor != null) + if (PhysActor != null) PhysActor.Building = true; UpdatePrimFlags(wasUsingPhysics, wasTemporary, wasPhantom, makeVolumeDetect, false); } @@ -5563,7 +5567,7 @@ public bool RemoveAnimation(string anim) public int ClearObjectAnimations() { int ret = 0; - if(Animations != null) + if (Animations != null) { ret = Animations.Count; Animations.Clear(); @@ -5590,7 +5594,7 @@ public int GetAnimations(out UUID[] ids, out int[] seqs) { if (Animations == null) return -1; - if(Animations.Count == 0) + if (Animations.Count == 0) return 0; ids = new UUID[Animations.Count]; Animations.Keys.CopyTo(ids, 0); @@ -5615,18 +5619,18 @@ public Byte[] SerializeAnimations() byte[] tmp = Utils.UInt16ToBytes((ushort)Animations.Count); ms.Write(tmp, 0, 2); - foreach(KeyValuePair kvp in AnimationsNames) + foreach (KeyValuePair kvp in AnimationsNames) { tmp = kvp.Key.GetBytes(); ms.Write(tmp, 0, 16); - if(string.IsNullOrEmpty(kvp.Value)) + if (string.IsNullOrEmpty(kvp.Value)) ms.WriteByte(0); else { byte[] str = Util.StringToBytes(kvp.Value, 64); int len = str.Length - 1; ms.WriteByte((byte)len); - ms.Write(str, 0 , len); + ms.Write(str, 0, len); } } return ms.ToArray(); @@ -5653,21 +5657,21 @@ public void DeSerializeAnimations(Byte[] data) try { int count = (int)Utils.BytesToUInt16(data, 0); - if(count == 0) + if (count == 0) return; Animations = new Dictionary(count); AnimationsNames = new Dictionary(count); int pos = 2; - while(--count >= 0) + while (--count >= 0) { UUID id = new(data, pos); - if(id.IsZero()) + if (id.IsZero()) break; pos += 16; int strlen = data[pos++]; string name = UTF8Encoding.UTF8.GetString(data, pos, strlen); - if(string.IsNullOrEmpty(name)) + if (string.IsNullOrEmpty(name)) break; pos += strlen; Animations[id] = NextObjectAnimationSequenceNumber; @@ -5683,13 +5687,9 @@ public void DeSerializeAnimations(Byte[] data) public string SerializeLinksetData() { - if (IsRoot && (LinksetData != null)) + if (LinksetData is not null) { - if (LinksetData.Count() > 0) - { - //return JsonSerializer.Serialize(LinksetData); - return LinksetData.SerializeLinksetData(); - } + return LinksetData.SerializeLinksetData(); } return string.Empty; @@ -5700,9 +5700,7 @@ public void DeserializeLinksetData(string data) if (string.IsNullOrWhiteSpace(data)) return; - //LinksetData = JsonSerializer.Deserialize(data); - LinksetData ??= new(); - LinksetData.DeserializeLinksetData(data); + LinksetData = LinksetData.DeserializeLinksetData(data); } public bool GetOwnerName(out string FirstName, out string LastName) diff --git a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs index 04cba59fc8d..5632745834d 100644 --- a/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs +++ b/OpenSim/Region/Framework/Scenes/Serialization/SceneObjectSerializer.cs @@ -820,9 +820,6 @@ private static void ProcessLinksetData(SceneObjectPart obj, XmlReader reader) try { string data = reader.ReadElementContentAsString(); - if (string.IsNullOrEmpty(data)) - return; - obj.DeserializeLinksetData(data); } catch @@ -1683,7 +1680,9 @@ public static void SOPToXml2(XmlTextWriter writer, SceneObjectPart sop, Dictiona if(Math.Abs(sop.SitActiveRange) > 1e-5) writer.WriteElementString("SitActRange", sop.SitActiveRange.ToString(Culture.FormatProvider)); - writer.WriteElementString("LinksetData", sop.SerializeLinksetData()); + var lsd = sop.SerializeLinksetData(); + if (string.IsNullOrWhiteSpace(lsd) is false) + writer.WriteElementString("LinksetData", lsd); writer.WriteEndElement(); } diff --git a/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs b/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs index 79a57a5faa5..5130000f614 100644 --- a/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs +++ b/OpenSim/Region/OptionalModules/UserStatistics/WebStatsModule.cs @@ -76,6 +76,10 @@ public class WebStatsModule : ISharedRegionModule private string m_loglines = String.Empty; private volatile int lastHit = 12000; + public WebStatsModule() + { + } + #region ISharedRegionModule public virtual void Initialise(IConfigSource config) @@ -764,7 +768,7 @@ f_send_packet INT NOT NULL DEFAULT '0' ) "; - #endregion + #endregion } diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 2f706257fd8..0af35d35b6a 100755 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -105,7 +105,7 @@ public class AutoBackupModule : ISharedRegionModule private bool m_enabled; private ICommandConsole m_console; private List m_Scenes = new List (); - private Timer m_masterTimer; + private System.Timers.Timer m_masterTimer; private bool m_busy; private int m_KeepFilesForDays = -1; private string m_backupDir; @@ -167,7 +167,7 @@ public void Initialise(IConfigSource source) m_log.Debug(m_defaultState.ToString()); m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); - m_masterTimer = new Timer(); + m_masterTimer = new System.Timers.Timer(); m_masterTimer.Interval = m_baseInterval; m_masterTimer.Elapsed += HandleElapsed; m_masterTimer.AutoReset = false; diff --git a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs index 23a3ac6dced..38fae2d185b 100644 --- a/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs +++ b/OpenSim/Region/ScriptEngine/Shared/Api/Implementation/LSL_Api.cs @@ -266,8 +266,6 @@ public int LlRequestAgentDataCacheTimeoutMs private string m_lsl_shard = "OpenSim"; private string m_lsl_user_agent = string.Empty; - private int m_linksetDataLimit = 32 * 1024; - private static readonly Dictionary MovementAnimationsForLSL = new(StringComparer.InvariantCultureIgnoreCase) { {"CROUCH", "Crouching"}, @@ -446,8 +444,6 @@ private void LoadConfig() m_AllowGodFunctions = seConfig.GetBoolean("AllowGodFunctions", false); m_disable_underground_movement = seConfig.GetBoolean("DisableUndergroundMovement", true); - - m_linksetDataLimit = seConfig.GetInt("LinksetDataLimit", m_linksetDataLimit); } if (m_notecardLineReadCharsMax > 65535) @@ -18605,7 +18601,7 @@ public LSL_Integer llLinksetDataAvailable() rootPrim.LinksetData = new LinksetData(); } - return new LSL_Integer(rootPrim.LinksetData.LinksetDataBytesFree); + return new LSL_Integer(rootPrim.LinksetData.BytesFree); } public LSL_Integer llLinksetDataCountKeys() diff --git a/Source/OpenSim.Data.Model/Core/AgentPref.cs b/Source/OpenSim.Data.Model/Core/AgentPref.cs new file mode 100644 index 00000000000..6efaa55e3ec --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/AgentPref.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class AgentPref +{ + public string PrincipalId { get; set; } + + public string AccessPrefs { get; set; } + + public double HoverHeight { get; set; } + + public string Language { get; set; } + + public bool? LanguageIsPublic { get; set; } + + public int PermEveryone { get; set; } + + public int PermGroup { get; set; } + + public int PermNextOwner { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Asset.cs b/Source/OpenSim.Data.Model/Core/Asset.cs new file mode 100644 index 00000000000..30085440ec7 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Asset.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Asset +{ + public string Name { get; set; } + + public string Description { get; set; } + + public sbyte AssetType { get; set; } + + public bool Local { get; set; } + + public bool Temporary { get; set; } + + public byte[] Data { get; set; } + + public string Id { get; set; } + + public int? CreateTime { get; set; } + + public int? AccessTime { get; set; } + + public int AssetFlags { get; set; } + + public string CreatorId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Auth.cs b/Source/OpenSim.Data.Model/Core/Auth.cs new file mode 100644 index 00000000000..64102038ee8 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Auth.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Auth +{ + public string Uuid { get; set; } + + public string PasswordHash { get; set; } + + public string PasswordSalt { get; set; } + + public string WebLoginKey { get; set; } + + public string AccountType { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Avatar.cs b/Source/OpenSim.Data.Model/Core/Avatar.cs new file mode 100644 index 00000000000..1fc5e5dd211 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Avatar.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Avatar +{ + public string PrincipalId { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Classified.cs b/Source/OpenSim.Data.Model/Core/Classified.cs new file mode 100644 index 00000000000..24cf459b206 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Classified.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Classified +{ + public string Classifieduuid { get; set; } + + public string Creatoruuid { get; set; } + + public int Creationdate { get; set; } + + public int Expirationdate { get; set; } + + public string Category { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Parceluuid { get; set; } + + public int Parentestate { get; set; } + + public string Snapshotuuid { get; set; } + + public string Simname { get; set; } + + public string Posglobal { get; set; } + + public string Parcelname { get; set; } + + public int Classifiedflags { get; set; } + + public int Priceforlisting { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/EstateGroup.cs b/Source/OpenSim.Data.Model/Core/EstateGroup.cs new file mode 100644 index 00000000000..fecb13c40b5 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/EstateGroup.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class EstateGroup +{ + public uint EstateId { get; set; } + + public string Uuid { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/EstateManager.cs b/Source/OpenSim.Data.Model/Core/EstateManager.cs new file mode 100644 index 00000000000..91893b1ce1f --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/EstateManager.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class EstateManager +{ + public uint EstateId { get; set; } + + public string Uuid { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/EstateMap.cs b/Source/OpenSim.Data.Model/Core/EstateMap.cs new file mode 100644 index 00000000000..21753573857 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/EstateMap.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class EstateMap +{ + public string RegionId { get; set; } + + public int EstateId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/EstateSetting.cs b/Source/OpenSim.Data.Model/Core/EstateSetting.cs new file mode 100644 index 00000000000..cc8726c0f2a --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/EstateSetting.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class EstateSetting +{ + public uint EstateId { get; set; } + + public string EstateName { get; set; } + + public sbyte AbuseEmailToEstateOwner { get; set; } + + public sbyte DenyAnonymous { get; set; } + + public sbyte ResetHomeOnTeleport { get; set; } + + public sbyte FixedSun { get; set; } + + public sbyte DenyTransacted { get; set; } + + public sbyte BlockDwell { get; set; } + + public sbyte DenyIdentified { get; set; } + + public sbyte AllowVoice { get; set; } + + public sbyte UseGlobalTime { get; set; } + + public int PricePerMeter { get; set; } + + public sbyte TaxFree { get; set; } + + public sbyte AllowDirectTeleport { get; set; } + + public int RedirectGridX { get; set; } + + public int RedirectGridY { get; set; } + + public uint ParentEstateId { get; set; } + + public double SunPosition { get; set; } + + public sbyte EstateSkipScripts { get; set; } + + public float BillableFactor { get; set; } + + public sbyte PublicAccess { get; set; } + + public string AbuseEmail { get; set; } + + public string EstateOwner { get; set; } + + public sbyte DenyMinors { get; set; } + + public sbyte AllowLandmark { get; set; } + + public sbyte AllowParcelChanges { get; set; } + + public sbyte AllowSetHome { get; set; } + + public sbyte AllowEnviromentOverride { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/EstateUser.cs b/Source/OpenSim.Data.Model/Core/EstateUser.cs new file mode 100644 index 00000000000..68999c66285 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/EstateUser.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class EstateUser +{ + public uint EstateId { get; set; } + + public string Uuid { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Estateban.cs b/Source/OpenSim.Data.Model/Core/Estateban.cs new file mode 100644 index 00000000000..c8df10794d2 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Estateban.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Estateban +{ + public uint EstateId { get; set; } + + public string BannedUuid { get; set; } + + public string BannedIp { get; set; } + + public string BannedIpHostMask { get; set; } + + public string BannedNameMask { get; set; } + + public string BanningUuid { get; set; } + + public int BanTime { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Friend.cs b/Source/OpenSim.Data.Model/Core/Friend.cs new file mode 100644 index 00000000000..4f72f0be9cf --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Friend.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Friend +{ + public string PrincipalId { get; set; } + + public string Friend1 { get; set; } + + public string Flags { get; set; } + + public string Offered { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Fsasset.cs b/Source/OpenSim.Data.Model/Core/Fsasset.cs new file mode 100644 index 00000000000..86975e0cd76 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Fsasset.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Fsasset +{ + public string Id { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public int Type { get; set; } + + public string Hash { get; set; } + + public int CreateTime { get; set; } + + public int AccessTime { get; set; } + + public int AssetFlags { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/GloebitSubscription.cs b/Source/OpenSim.Data.Model/Core/GloebitSubscription.cs new file mode 100644 index 00000000000..d7f179bc14e --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/GloebitSubscription.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class GloebitSubscription +{ + public string SubscriptionId { get; set; } + + public string ObjectId { get; set; } + + public string AppKey { get; set; } + + public string GlbApiUrl { get; set; } + + public bool Enabled { get; set; } + + public string ObjectName { get; set; } + + public string Description { get; set; } + + public DateTime CTime { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/GloebitTransaction.cs b/Source/OpenSim.Data.Model/Core/GloebitTransaction.cs new file mode 100644 index 00000000000..2f3ece7fb25 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/GloebitTransaction.cs @@ -0,0 +1,61 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class GloebitTransaction +{ + public string TransactionId { get; set; } + + public string PayerId { get; set; } + + public string PayerName { get; set; } + + public string PayeeId { get; set; } + + public string PayeeName { get; set; } + + public int Amount { get; set; } + + public int TransactionType { get; set; } + + public string TransactionTypeString { get; set; } + + public bool IsSubscriptionDebit { get; set; } + + public string SubscriptionId { get; set; } + + public string PartId { get; set; } + + public string PartName { get; set; } + + public string PartDescription { get; set; } + + public string CategoryId { get; set; } + + public int? SaleType { get; set; } + + public bool Submitted { get; set; } + + public bool ResponseReceived { get; set; } + + public bool ResponseSuccess { get; set; } + + public string ResponseStatus { get; set; } + + public string ResponseReason { get; set; } + + public int PayerEndingBalance { get; set; } + + public bool Enacted { get; set; } + + public bool Consumed { get; set; } + + public bool Canceled { get; set; } + + public DateTime CTime { get; set; } + + public DateTime? EnactedTime { get; set; } + + public DateTime? FinishedTime { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/GloebitUser.cs b/Source/OpenSim.Data.Model/Core/GloebitUser.cs new file mode 100644 index 00000000000..fe7e40ea8bc --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/GloebitUser.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class GloebitUser +{ + public string AppKey { get; set; } + + public string PrincipalId { get; set; } + + public string GloebitId { get; set; } + + public string GloebitToken { get; set; } + + public string LastSessionId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/GridUser.cs b/Source/OpenSim.Data.Model/Core/GridUser.cs new file mode 100644 index 00000000000..9aa43e4c36d --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/GridUser.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class GridUser +{ + public string UserId { get; set; } + + public string HomeRegionId { get; set; } + + public string HomePosition { get; set; } + + public string HomeLookAt { get; set; } + + public string LastRegionId { get; set; } + + public string LastPosition { get; set; } + + public string LastLookAt { get; set; } + + public string Online { get; set; } + + public string Login { get; set; } + + public string Logout { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/HgTravelingDatum.cs b/Source/OpenSim.Data.Model/Core/HgTravelingDatum.cs new file mode 100644 index 00000000000..e63f766e8b3 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/HgTravelingDatum.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class HgTravelingDatum +{ + public string SessionId { get; set; } + + public string UserId { get; set; } + + public string GridExternalName { get; set; } + + public string ServiceToken { get; set; } + + public string ClientIpaddress { get; set; } + + public string MyIpaddress { get; set; } + + public DateTime Tmstamp { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/ImOffline.cs b/Source/OpenSim.Data.Model/Core/ImOffline.cs new file mode 100644 index 00000000000..dda98581f2b --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/ImOffline.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class ImOffline +{ + public int Id { get; set; } + + public string PrincipalId { get; set; } + + public string FromId { get; set; } + + public string Message { get; set; } + + public DateTime Tmstamp { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Inventoryfolder.cs b/Source/OpenSim.Data.Model/Core/Inventoryfolder.cs new file mode 100644 index 00000000000..7a9e07f884c --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Inventoryfolder.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Inventoryfolder +{ + public string FolderName { get; set; } + + public short Type { get; set; } + + public int Version { get; set; } + + public string FolderId { get; set; } + + public string AgentId { get; set; } + + public string ParentFolderId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Inventoryitem.cs b/Source/OpenSim.Data.Model/Core/Inventoryitem.cs new file mode 100644 index 00000000000..71bd8d70a3a --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Inventoryitem.cs @@ -0,0 +1,47 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Inventoryitem +{ + public string AssetId { get; set; } + + public int? AssetType { get; set; } + + public string InventoryName { get; set; } + + public string InventoryDescription { get; set; } + + public uint? InventoryNextPermissions { get; set; } + + public uint? InventoryCurrentPermissions { get; set; } + + public int? InvType { get; set; } + + public string CreatorId { get; set; } + + public uint InventoryBasePermissions { get; set; } + + public uint InventoryEveryOnePermissions { get; set; } + + public int SalePrice { get; set; } + + public sbyte SaleType { get; set; } + + public int CreationDate { get; set; } + + public string GroupId { get; set; } + + public sbyte GroupOwned { get; set; } + + public uint Flags { get; set; } + + public string InventoryId { get; set; } + + public string AvatarId { get; set; } + + public string ParentFolderId { get; set; } + + public uint InventoryGroupPermissions { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Migration.cs b/Source/OpenSim.Data.Model/Core/Migration.cs new file mode 100644 index 00000000000..bd0189cdbae --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Migration.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Migration +{ + public string Name { get; set; } + + public int? Version { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/MuteList.cs b/Source/OpenSim.Data.Model/Core/MuteList.cs new file mode 100644 index 00000000000..2eaf49d5621 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/MuteList.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class MuteList +{ + public string AgentId { get; set; } + + public string MuteId { get; set; } + + public string MuteName { get; set; } + + public int MuteType { get; set; } + + public int MuteFlags { get; set; } + + public int Stamp { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OpenSimCoreContext.cs b/Source/OpenSim.Data.Model/Core/OpenSimCoreContext.cs new file mode 100644 index 00000000000..58547bfd3ec --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OpenSimCoreContext.cs @@ -0,0 +1,1642 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace OpenSim.Data.Model.Core; + +public partial class OpenSimCoreContext : DbContext +{ + public OpenSimCoreContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet AgentPrefs { get; set; } + + public virtual DbSet Assets { get; set; } + + public virtual DbSet Auths { get; set; } + + public virtual DbSet Avatars { get; set; } + + public virtual DbSet Classifieds { get; set; } + + public virtual DbSet EstateGroups { get; set; } + + public virtual DbSet EstateManagers { get; set; } + + public virtual DbSet EstateMaps { get; set; } + + public virtual DbSet EstateSettings { get; set; } + + public virtual DbSet EstateUsers { get; set; } + + public virtual DbSet Estatebans { get; set; } + + public virtual DbSet Friends { get; set; } + + public virtual DbSet Fsassets { get; set; } + + public virtual DbSet GloebitSubscriptions { get; set; } + + public virtual DbSet GloebitTransactions { get; set; } + + public virtual DbSet GloebitUsers { get; set; } + + public virtual DbSet GridUsers { get; set; } + + public virtual DbSet HgTravelingData { get; set; } + + public virtual DbSet ImOfflines { get; set; } + + public virtual DbSet Inventoryfolders { get; set; } + + public virtual DbSet Inventoryitems { get; set; } + + public virtual DbSet Migrations { get; set; } + + public virtual DbSet MuteLists { get; set; } + + public virtual DbSet OsGroupsGroups { get; set; } + + public virtual DbSet OsGroupsInvites { get; set; } + + public virtual DbSet OsGroupsMemberships { get; set; } + + public virtual DbSet OsGroupsNotices { get; set; } + + public virtual DbSet OsGroupsPrincipals { get; set; } + + public virtual DbSet OsGroupsRoles { get; set; } + + public virtual DbSet OsGroupsRolememberships { get; set; } + + public virtual DbSet Presences { get; set; } + + public virtual DbSet Regions { get; set; } + + public virtual DbSet Tokens { get; set; } + + public virtual DbSet UserAccounts { get; set; } + + public virtual DbSet UserAliases { get; set; } + + public virtual DbSet Userdata { get; set; } + + public virtual DbSet Usernotes { get; set; } + + public virtual DbSet Userpicks { get; set; } + + public virtual DbSet Userprofiles { get; set; } + + public virtual DbSet Usersettings { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder + .UseCollation("utf8mb4_0900_ai_ci") + .HasCharSet("utf8mb4"); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.PrincipalId).HasName("PRIMARY"); + + entity + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID").IsUnique(); + + entity.Property(e => e.PrincipalId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PrincipalID"); + entity.Property(e => e.AccessPrefs) + .IsRequired() + .HasMaxLength(2) + .HasDefaultValueSql("'M'") + .IsFixedLength(); + entity.Property(e => e.HoverHeight).HasColumnType("double(30,27)"); + entity.Property(e => e.Language) + .IsRequired() + .HasMaxLength(5) + .HasDefaultValueSql("'en-us'") + .IsFixedLength(); + entity.Property(e => e.LanguageIsPublic) + .IsRequired() + .HasDefaultValueSql("'1'"); + entity.Property(e => e.PermNextOwner).HasDefaultValueSql("'532480'"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity + .ToTable("assets") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Id) + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("id"); + entity.Property(e => e.AccessTime) + .HasDefaultValueSql("'0'") + .HasColumnName("access_time"); + entity.Property(e => e.AssetFlags).HasColumnName("asset_flags"); + entity.Property(e => e.AssetType).HasColumnName("assetType"); + entity.Property(e => e.CreateTime) + .HasDefaultValueSql("'0'") + .HasColumnName("create_time"); + entity.Property(e => e.CreatorId) + .IsRequired() + .HasMaxLength(128) + .HasDefaultValueSql("''") + .HasColumnName("CreatorID"); + entity.Property(e => e.Data) + .IsRequired() + .HasColumnName("data"); + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(64) + .HasColumnName("description"); + entity.Property(e => e.Local).HasColumnName("local"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(64) + .HasColumnName("name"); + entity.Property(e => e.Temporary).HasColumnName("temporary"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("auth") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Uuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("UUID"); + entity.Property(e => e.AccountType) + .IsRequired() + .HasMaxLength(32) + .HasDefaultValueSql("'UserAccount'") + .HasColumnName("accountType"); + entity.Property(e => e.PasswordHash) + .IsRequired() + .HasMaxLength(32) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("passwordHash"); + entity.Property(e => e.PasswordSalt) + .IsRequired() + .HasMaxLength(32) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("passwordSalt"); + entity.Property(e => e.WebLoginKey) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .HasColumnName("webLoginKey"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.PrincipalId, e.Name }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID"); + + entity.Property(e => e.PrincipalId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PrincipalID"); + entity.Property(e => e.Name).HasMaxLength(32); + entity.Property(e => e.Value).HasColumnType("text"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Classifieduuid).HasName("PRIMARY"); + + entity + .ToTable("classifieds") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.Classifieduuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("classifieduuid"); + entity.Property(e => e.Category) + .IsRequired() + .HasMaxLength(20) + .HasColumnName("category"); + entity.Property(e => e.Classifiedflags).HasColumnName("classifiedflags"); + entity.Property(e => e.Creationdate).HasColumnName("creationdate"); + entity.Property(e => e.Creatoruuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("creatoruuid"); + entity.Property(e => e.Description) + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + entity.Property(e => e.Expirationdate).HasColumnName("expirationdate"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("name"); + entity.Property(e => e.Parcelname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("parcelname"); + entity.Property(e => e.Parceluuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parceluuid"); + entity.Property(e => e.Parentestate).HasColumnName("parentestate"); + entity.Property(e => e.Posglobal) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("posglobal"); + entity.Property(e => e.Priceforlisting).HasColumnName("priceforlisting"); + entity.Property(e => e.Simname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("simname"); + entity.Property(e => e.Snapshotuuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("snapshotuuid"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("estate_groups") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.EstateId, "EstateID"); + + entity.Property(e => e.EstateId).HasColumnName("EstateID"); + entity.Property(e => e.Uuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("uuid"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("estate_managers") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.EstateId, "EstateID"); + + entity.Property(e => e.EstateId).HasColumnName("EstateID"); + entity.Property(e => e.Uuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("uuid"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.RegionId).HasName("PRIMARY"); + + entity + .ToTable("estate_map") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.EstateId, "EstateID"); + + entity.Property(e => e.RegionId) + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("RegionID"); + entity.Property(e => e.EstateId).HasColumnName("EstateID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.EstateId).HasName("PRIMARY"); + + entity + .ToTable("estate_settings") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.EstateId).HasColumnName("EstateID"); + entity.Property(e => e.AbuseEmail) + .IsRequired() + .HasMaxLength(255); + entity.Property(e => e.AllowLandmark).HasDefaultValueSql("'1'"); + entity.Property(e => e.AllowParcelChanges).HasDefaultValueSql("'1'"); + entity.Property(e => e.AllowSetHome).HasDefaultValueSql("'1'"); + entity.Property(e => e.EstateName).HasMaxLength(64); + entity.Property(e => e.EstateOwner) + .IsRequired() + .HasMaxLength(36); + entity.Property(e => e.ParentEstateId).HasColumnName("ParentEstateID"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("estate_users") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.EstateId, "EstateID"); + + entity.Property(e => e.EstateId).HasColumnName("EstateID"); + entity.Property(e => e.Uuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("uuid"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("estateban") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.EstateId, "estateban_EstateID"); + + entity.Property(e => e.BanTime).HasColumnName("banTime"); + entity.Property(e => e.BannedIp) + .IsRequired() + .HasMaxLength(16) + .HasColumnName("bannedIp"); + entity.Property(e => e.BannedIpHostMask) + .IsRequired() + .HasMaxLength(16) + .HasColumnName("bannedIpHostMask"); + entity.Property(e => e.BannedNameMask) + .HasMaxLength(64) + .HasColumnName("bannedNameMask"); + entity.Property(e => e.BannedUuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("bannedUUID"); + entity.Property(e => e.BanningUuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .HasColumnName("banningUUID"); + entity.Property(e => e.EstateId).HasColumnName("EstateID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.PrincipalId, e.Friend1 }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 36, 36 }); + + entity + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID"); + + entity.Property(e => e.PrincipalId) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .HasColumnName("PrincipalID"); + entity.Property(e => e.Friend1).HasColumnName("Friend"); + entity.Property(e => e.Flags) + .IsRequired() + .HasMaxLength(16) + .HasDefaultValueSql("'0'"); + entity.Property(e => e.Offered) + .IsRequired() + .HasMaxLength(32) + .HasDefaultValueSql("'0'"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity.HasIndex(e => e.AccessTime, "idx_fsassets_access_time"); + + entity + .ToTable("fsassets") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Id) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("id"); + entity.Property(e => e.AccessTime).HasColumnName("access_time"); + entity.Property(e => e.AssetFlags).HasColumnName("asset_flags"); + entity.Property(e => e.CreateTime).HasColumnName("create_time"); + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("''") + .HasColumnName("description"); + entity.Property(e => e.Hash) + .IsRequired() + .HasMaxLength(80) + .IsFixedLength() + .HasColumnName("hash"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("''") + .HasColumnName("name"); + entity.Property(e => e.Type).HasColumnName("type"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.ObjectId, e.AppKey, e.GlbApiUrl }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0, 0 }); + + entity + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.CTime, "ix_cts"); + + entity.HasIndex(e => e.ObjectId, "ix_oid"); + + entity.HasIndex(e => e.SubscriptionId, "ix_sid"); + + entity.HasIndex(e => new { e.SubscriptionId, e.GlbApiUrl }, "k_sub_api").IsUnique(); + + entity.Property(e => e.ObjectId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("ObjectID"); + entity.Property(e => e.AppKey).HasMaxLength(64); + entity.Property(e => e.GlbApiUrl).HasMaxLength(100); + entity.Property(e => e.CTime) + .ValueGeneratedOnAddOrUpdate() + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .HasColumnType("timestamp") + .HasColumnName("cTime"); + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(255); + entity.Property(e => e.Enabled).HasColumnName("enabled"); + entity.Property(e => e.ObjectName).HasMaxLength(64); + entity.Property(e => e.SubscriptionId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("SubscriptionID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.TransactionId).HasName("PRIMARY"); + + entity + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.CTime, "ix_cts"); + + entity.HasIndex(e => e.PayeeId, "ix_payeeid"); + + entity.HasIndex(e => e.PayerId, "ix_payerid"); + + entity.HasIndex(e => e.PartId, "ix_pid"); + + entity.HasIndex(e => e.SubscriptionId, "ix_sid"); + + entity.HasIndex(e => e.TransactionType, "ix_tt"); + + entity.Property(e => e.TransactionId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("TransactionID"); + entity.Property(e => e.CTime) + .ValueGeneratedOnAddOrUpdate() + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .HasColumnType("timestamp") + .HasColumnName("cTime"); + entity.Property(e => e.Canceled).HasColumnName("canceled"); + entity.Property(e => e.CategoryId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("CategoryID"); + entity.Property(e => e.Consumed).HasColumnName("consumed"); + entity.Property(e => e.Enacted).HasColumnName("enacted"); + entity.Property(e => e.EnactedTime) + .HasColumnType("timestamp") + .HasColumnName("enactedTime"); + entity.Property(e => e.FinishedTime) + .HasColumnType("timestamp") + .HasColumnName("finishedTime"); + entity.Property(e => e.PartDescription).HasMaxLength(128); + entity.Property(e => e.PartId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PartID"); + entity.Property(e => e.PartName).HasMaxLength(64); + entity.Property(e => e.PayeeId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PayeeID"); + entity.Property(e => e.PayeeName) + .IsRequired() + .HasMaxLength(255); + entity.Property(e => e.PayerId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PayerID"); + entity.Property(e => e.PayerName) + .IsRequired() + .HasMaxLength(255); + entity.Property(e => e.ResponseReason) + .IsRequired() + .HasMaxLength(255); + entity.Property(e => e.ResponseStatus) + .IsRequired() + .HasMaxLength(64); + entity.Property(e => e.SubscriptionId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("SubscriptionID"); + entity.Property(e => e.TransactionTypeString) + .IsRequired() + .HasMaxLength(64); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.AppKey, e.PrincipalId }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.PrincipalId, "ix_gu_pid"); + + entity.Property(e => e.AppKey) + .HasMaxLength(36) + .IsFixedLength(); + entity.Property(e => e.PrincipalId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PrincipalID"); + entity.Property(e => e.GloebitId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("GloebitID"); + entity.Property(e => e.GloebitToken) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength(); + entity.Property(e => e.LastSessionId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("LastSessionID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.UserId).HasName("PRIMARY"); + + entity + .ToTable("GridUser") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.UserId).HasColumnName("UserID"); + entity.Property(e => e.HomeLookAt) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("'<0,0,0>'") + .IsFixedLength(); + entity.Property(e => e.HomePosition) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("'<0,0,0>'") + .IsFixedLength(); + entity.Property(e => e.HomeRegionId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("HomeRegionID"); + entity.Property(e => e.LastLookAt) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("'<0,0,0>'") + .IsFixedLength(); + entity.Property(e => e.LastPosition) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("'<0,0,0>'") + .IsFixedLength(); + entity.Property(e => e.LastRegionId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("LastRegionID"); + entity.Property(e => e.Login) + .IsRequired() + .HasMaxLength(16) + .HasDefaultValueSql("'0'") + .IsFixedLength(); + entity.Property(e => e.Logout) + .IsRequired() + .HasMaxLength(16) + .HasDefaultValueSql("'0'") + .IsFixedLength(); + entity.Property(e => e.Online) + .IsRequired() + .HasMaxLength(5) + .HasDefaultValueSql("'false'") + .IsFixedLength(); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.SessionId).HasName("PRIMARY"); + + entity + .ToTable("hg_traveling_data") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.UserId, "UserID"); + + entity.Property(e => e.SessionId) + .HasMaxLength(36) + .HasColumnName("SessionID"); + entity.Property(e => e.ClientIpaddress) + .IsRequired() + .HasMaxLength(16) + .HasDefaultValueSql("''") + .HasColumnName("ClientIPAddress"); + entity.Property(e => e.GridExternalName) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''"); + entity.Property(e => e.MyIpaddress) + .IsRequired() + .HasMaxLength(16) + .HasDefaultValueSql("''") + .HasColumnName("MyIPAddress"); + entity.Property(e => e.ServiceToken) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''"); + entity.Property(e => e.Tmstamp) + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .HasColumnType("timestamp") + .HasColumnName("TMStamp"); + entity.Property(e => e.UserId) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("UserID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity + .ToTable("im_offline") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.FromId, "FromID"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID"); + + entity.Property(e => e.Id) + .HasColumnType("mediumint") + .HasColumnName("ID"); + entity.Property(e => e.FromId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("FromID"); + entity.Property(e => e.Message) + .IsRequired() + .HasColumnType("text"); + entity.Property(e => e.PrincipalId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("PrincipalID"); + entity.Property(e => e.Tmstamp) + .ValueGeneratedOnAddOrUpdate() + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .HasColumnType("timestamp") + .HasColumnName("TMStamp"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.FolderId).HasName("PRIMARY"); + + entity + .ToTable("inventoryfolders") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.AgentId, "inventoryfolders_agentid"); + + entity.HasIndex(e => e.ParentFolderId, "inventoryfolders_parentFolderid"); + + entity.Property(e => e.FolderId) + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("folderID"); + entity.Property(e => e.AgentId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("agentID"); + entity.Property(e => e.FolderName) + .HasMaxLength(64) + .HasColumnName("folderName"); + entity.Property(e => e.ParentFolderId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parentFolderID"); + entity.Property(e => e.Type).HasColumnName("type"); + entity.Property(e => e.Version).HasColumnName("version"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.InventoryId).HasName("PRIMARY"); + + entity + .ToTable("inventoryitems") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.AvatarId, "inventoryitems_avatarid"); + + entity.HasIndex(e => e.ParentFolderId, "inventoryitems_parentFolderid"); + + entity.Property(e => e.InventoryId) + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("inventoryID"); + entity.Property(e => e.AssetId) + .HasMaxLength(36) + .HasColumnName("assetID"); + entity.Property(e => e.AssetType).HasColumnName("assetType"); + entity.Property(e => e.AvatarId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("avatarID"); + entity.Property(e => e.CreationDate).HasColumnName("creationDate"); + entity.Property(e => e.CreatorId) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .HasColumnName("creatorID"); + entity.Property(e => e.Flags).HasColumnName("flags"); + entity.Property(e => e.GroupId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .HasColumnName("groupID"); + entity.Property(e => e.GroupOwned).HasColumnName("groupOwned"); + entity.Property(e => e.InvType).HasColumnName("invType"); + entity.Property(e => e.InventoryBasePermissions).HasColumnName("inventoryBasePermissions"); + entity.Property(e => e.InventoryCurrentPermissions).HasColumnName("inventoryCurrentPermissions"); + entity.Property(e => e.InventoryDescription) + .HasMaxLength(128) + .HasColumnName("inventoryDescription"); + entity.Property(e => e.InventoryEveryOnePermissions).HasColumnName("inventoryEveryOnePermissions"); + entity.Property(e => e.InventoryGroupPermissions).HasColumnName("inventoryGroupPermissions"); + entity.Property(e => e.InventoryName) + .HasMaxLength(64) + .HasColumnName("inventoryName"); + entity.Property(e => e.InventoryNextPermissions).HasColumnName("inventoryNextPermissions"); + entity.Property(e => e.ParentFolderId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parentFolderID"); + entity.Property(e => e.SalePrice).HasColumnName("salePrice"); + entity.Property(e => e.SaleType).HasColumnName("saleType"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("migrations") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Name) + .HasMaxLength(100) + .HasColumnName("name"); + entity.Property(e => e.Version).HasColumnName("version"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("MuteList") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.AgentId, "AgentID"); + + entity.HasIndex(e => new { e.AgentId, e.MuteId, e.MuteName }, "AgentID_2").IsUnique(); + + entity.Property(e => e.AgentId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("AgentID"); + entity.Property(e => e.MuteId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("MuteID"); + entity.Property(e => e.MuteName) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("''"); + entity.Property(e => e.MuteType).HasDefaultValueSql("'1'"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.GroupId).HasName("PRIMARY"); + + entity + .ToTable("os_groups_groups") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.Name, "Name") + .IsUnique() + .HasAnnotation("MySql:FullTextIndex", true); + + entity.Property(e => e.GroupId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.Charter) + .IsRequired() + .HasColumnType("text") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.FounderId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("FounderID"); + entity.Property(e => e.InsigniaId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("InsigniaID"); + entity.Property(e => e.Location) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''"); + entity.Property(e => e.Name) + .IsRequired() + .HasDefaultValueSql("''") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.OpenEnrollment) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''"); + entity.Property(e => e.OwnerRoleId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("OwnerRoleID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.InviteId).HasName("PRIMARY"); + + entity + .ToTable("os_groups_invites") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => new { e.GroupId, e.PrincipalId }, "PrincipalGroup").IsUnique(); + + entity.Property(e => e.InviteId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("InviteID"); + entity.Property(e => e.GroupId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.PrincipalId) + .IsRequired() + .HasDefaultValueSql("''") + .HasColumnName("PrincipalID"); + entity.Property(e => e.RoleId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("RoleID"); + entity.Property(e => e.Tmstamp) + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .HasColumnType("timestamp") + .HasColumnName("TMStamp"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.GroupId, e.PrincipalId }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("os_groups_membership") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID"); + + entity.Property(e => e.GroupId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.PrincipalId) + .HasDefaultValueSql("''") + .HasColumnName("PrincipalID"); + entity.Property(e => e.AcceptNotices).HasDefaultValueSql("'1'"); + entity.Property(e => e.AccessToken) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength(); + entity.Property(e => e.ListInProfile).HasDefaultValueSql("'1'"); + entity.Property(e => e.SelectedRoleId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("SelectedRoleID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.NoticeId).HasName("PRIMARY"); + + entity + .ToTable("os_groups_notices") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.GroupId, "GroupID"); + + entity.HasIndex(e => e.Tmstamp, "TMStamp"); + + entity.Property(e => e.NoticeId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("NoticeID"); + entity.Property(e => e.AttachmentItemId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("AttachmentItemID"); + entity.Property(e => e.AttachmentName) + .IsRequired() + .HasMaxLength(128) + .HasDefaultValueSql("''"); + entity.Property(e => e.AttachmentOwnerId) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .HasColumnName("AttachmentOwnerID"); + entity.Property(e => e.FromName) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''"); + entity.Property(e => e.GroupId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.Message) + .IsRequired() + .HasColumnType("text") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.Subject) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.Tmstamp).HasColumnName("TMStamp"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.PrincipalId).HasName("PRIMARY"); + + entity + .ToTable("os_groups_principals") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.PrincipalId) + .HasDefaultValueSql("''") + .HasColumnName("PrincipalID"); + entity.Property(e => e.ActiveGroupId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("ActiveGroupID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.GroupId, e.RoleId }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("os_groups_roles") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.GroupId, "GroupID"); + + entity.Property(e => e.GroupId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.RoleId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("RoleID"); + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.Title) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.GroupId, e.RoleId, e.PrincipalId }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0, 0 }); + + entity + .ToTable("os_groups_rolemembership") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID"); + + entity.Property(e => e.GroupId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.RoleId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("RoleID"); + entity.Property(e => e.PrincipalId) + .HasDefaultValueSql("''") + .HasColumnName("PrincipalID"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("Presence") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.RegionId, "RegionID"); + + entity.HasIndex(e => e.SessionId, "SessionID").IsUnique(); + + entity.HasIndex(e => e.UserId, "UserID"); + + entity.Property(e => e.LastSeen) + .ValueGeneratedOnAddOrUpdate() + .HasDefaultValueSql("CURRENT_TIMESTAMP") + .HasColumnType("timestamp"); + entity.Property(e => e.RegionId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("RegionID"); + entity.Property(e => e.SecureSessionId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("SecureSessionID"); + entity.Property(e => e.SessionId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("SessionID"); + entity.Property(e => e.UserId) + .IsRequired() + .HasColumnName("UserID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("regions") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.ScopeId, "ScopeID"); + + entity.HasIndex(e => e.Flags, "flags"); + + entity.HasIndex(e => new { e.EastOverrideHandle, e.WestOverrideHandle, e.SouthOverrideHandle, e.NorthOverrideHandle }, "overrideHandles"); + + entity.HasIndex(e => e.RegionHandle, "regionHandle"); + + entity.HasIndex(e => e.RegionName, "regionName"); + + entity.Property(e => e.Uuid) + .HasMaxLength(36) + .HasColumnName("uuid"); + entity.Property(e => e.Access) + .HasDefaultValueSql("'1'") + .HasColumnName("access"); + entity.Property(e => e.EastOverrideHandle).HasColumnName("eastOverrideHandle"); + entity.Property(e => e.Flags).HasColumnName("flags"); + entity.Property(e => e.LastSeen).HasColumnName("last_seen"); + entity.Property(e => e.LocX).HasColumnName("locX"); + entity.Property(e => e.LocY).HasColumnName("locY"); + entity.Property(e => e.LocZ).HasColumnName("locZ"); + entity.Property(e => e.NorthOverrideHandle).HasColumnName("northOverrideHandle"); + entity.Property(e => e.OriginUuid) + .HasMaxLength(36) + .HasColumnName("originUUID"); + entity.Property(e => e.OwnerUuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .HasColumnName("owner_uuid"); + entity.Property(e => e.ParcelMapTexture) + .HasMaxLength(36) + .HasColumnName("parcelMapTexture"); + entity.Property(e => e.PrincipalId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("PrincipalID"); + entity.Property(e => e.RegionAssetRecvKey) + .HasMaxLength(128) + .HasColumnName("regionAssetRecvKey"); + entity.Property(e => e.RegionAssetSendKey) + .HasMaxLength(128) + .HasColumnName("regionAssetSendKey"); + entity.Property(e => e.RegionAssetUri) + .HasMaxLength(255) + .HasColumnName("regionAssetURI"); + entity.Property(e => e.RegionDataUri) + .HasMaxLength(255) + .HasColumnName("regionDataURI"); + entity.Property(e => e.RegionHandle).HasColumnName("regionHandle"); + entity.Property(e => e.RegionMapTexture) + .HasMaxLength(36) + .HasColumnName("regionMapTexture"); + entity.Property(e => e.RegionName) + .HasMaxLength(128) + .HasColumnName("regionName"); + entity.Property(e => e.RegionRecvKey) + .HasMaxLength(128) + .HasColumnName("regionRecvKey"); + entity.Property(e => e.RegionSecret) + .HasMaxLength(128) + .HasColumnName("regionSecret"); + entity.Property(e => e.RegionSendKey) + .HasMaxLength(128) + .HasColumnName("regionSendKey"); + entity.Property(e => e.RegionUserRecvKey) + .HasMaxLength(128) + .HasColumnName("regionUserRecvKey"); + entity.Property(e => e.RegionUserSendKey) + .HasMaxLength(128) + .HasColumnName("regionUserSendKey"); + entity.Property(e => e.RegionUserUri) + .HasMaxLength(255) + .HasColumnName("regionUserURI"); + entity.Property(e => e.ScopeId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("ScopeID"); + entity.Property(e => e.ServerHttpPort).HasColumnName("serverHttpPort"); + entity.Property(e => e.ServerIp) + .HasMaxLength(64) + .HasColumnName("serverIP"); + entity.Property(e => e.ServerPort).HasColumnName("serverPort"); + entity.Property(e => e.ServerRemotingPort).HasColumnName("serverRemotingPort"); + entity.Property(e => e.ServerUri) + .HasMaxLength(255) + .HasColumnName("serverURI"); + entity.Property(e => e.SizeX).HasColumnName("sizeX"); + entity.Property(e => e.SizeY).HasColumnName("sizeY"); + entity.Property(e => e.SouthOverrideHandle).HasColumnName("southOverrideHandle"); + entity.Property(e => e.Token) + .IsRequired() + .HasMaxLength(255); + entity.Property(e => e.WestOverrideHandle).HasColumnName("westOverrideHandle"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("tokens") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.Uuid, "UUID"); + + entity.HasIndex(e => e.Token1, "token"); + + entity.HasIndex(e => new { e.Uuid, e.Token1 }, "uuid_token").IsUnique(); + + entity.HasIndex(e => e.Validity, "validity"); + + entity.Property(e => e.Token1) + .IsRequired() + .HasColumnName("token"); + entity.Property(e => e.Uuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("UUID"); + entity.Property(e => e.Validity) + .HasColumnType("datetime") + .HasColumnName("validity"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.Email, "Email"); + + entity.HasIndex(e => e.FirstName, "FirstName"); + + entity.HasIndex(e => e.LastName, "LastName"); + + entity.HasIndex(e => new { e.FirstName, e.LastName }, "Name"); + + entity.HasIndex(e => e.PrincipalId, "PrincipalID").IsUnique(); + + entity.Property(e => e.Active) + .HasDefaultValueSql("'1'") + .HasColumnName("active"); + entity.Property(e => e.Email).HasMaxLength(64); + entity.Property(e => e.FirstName) + .IsRequired() + .HasMaxLength(64); + entity.Property(e => e.LastName) + .IsRequired() + .HasMaxLength(64); + entity.Property(e => e.PrincipalId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("PrincipalID"); + entity.Property(e => e.ScopeId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("ScopeID"); + entity.Property(e => e.ServiceUrls) + .HasColumnType("text") + .HasColumnName("ServiceURLs"); + entity.Property(e => e.UserTitle) + .IsRequired() + .HasMaxLength(64) + .HasDefaultValueSql("''") + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("UserAlias") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.AliasId, "AliasID").IsUnique(); + + entity.HasIndex(e => e.Id, "Id").IsUnique(); + + entity.HasIndex(e => e.UserId, "UserID"); + + entity.Property(e => e.AliasId) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("AliasID"); + entity.Property(e => e.Description).HasMaxLength(80); + entity.Property(e => e.UserId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("UserID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.UserId, e.TagId }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("userdata") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.UserId) + .HasMaxLength(36) + .IsFixedLength(); + entity.Property(e => e.TagId).HasMaxLength(64); + entity.Property(e => e.DataKey).HasMaxLength(255); + entity.Property(e => e.DataVal).HasMaxLength(255); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("usernotes") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => new { e.Useruuid, e.Targetuuid }, "useruuid").IsUnique(); + + entity.Property(e => e.Notes) + .IsRequired() + .HasColumnType("text") + .HasColumnName("notes"); + entity.Property(e => e.Targetuuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("targetuuid"); + entity.Property(e => e.Useruuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("useruuid"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Pickuuid).HasName("PRIMARY"); + + entity + .ToTable("userpicks") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.Pickuuid) + .HasMaxLength(36) + .HasColumnName("pickuuid"); + entity.Property(e => e.Creatoruuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("creatoruuid"); + entity.Property(e => e.Description) + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + entity.Property(e => e.Enabled) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("enabled"); + entity.Property(e => e.Gatekeeper) + .HasMaxLength(255) + .HasColumnName("gatekeeper"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("name"); + entity.Property(e => e.Originalname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("originalname"); + entity.Property(e => e.Parceluuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("parceluuid"); + entity.Property(e => e.Posglobal) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("posglobal"); + entity.Property(e => e.Simname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("simname"); + entity.Property(e => e.Snapshotuuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("snapshotuuid"); + entity.Property(e => e.Sortorder).HasColumnName("sortorder"); + entity.Property(e => e.Toppick) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("toppick"); + entity.Property(e => e.User) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("user"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Useruuid).HasName("PRIMARY"); + + entity + .ToTable("userprofile") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.Useruuid) + .HasMaxLength(36) + .HasColumnName("useruuid"); + entity.Property(e => e.ProfileAboutText) + .IsRequired() + .HasColumnType("text") + .HasColumnName("profileAboutText"); + entity.Property(e => e.ProfileAllowPublish) + .IsRequired() + .HasMaxLength(1) + .IsFixedLength() + .HasColumnName("profileAllowPublish"); + entity.Property(e => e.ProfileFirstImage) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("profileFirstImage"); + entity.Property(e => e.ProfileFirstText) + .IsRequired() + .HasColumnType("text") + .HasColumnName("profileFirstText"); + entity.Property(e => e.ProfileImage) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("profileImage"); + entity.Property(e => e.ProfileLanguages) + .IsRequired() + .HasColumnType("text") + .HasColumnName("profileLanguages"); + entity.Property(e => e.ProfileMaturePublish) + .IsRequired() + .HasMaxLength(1) + .IsFixedLength() + .HasColumnName("profileMaturePublish"); + entity.Property(e => e.ProfilePartner) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("profilePartner"); + entity.Property(e => e.ProfileSkillsMask).HasColumnName("profileSkillsMask"); + entity.Property(e => e.ProfileSkillsText) + .IsRequired() + .HasColumnType("text") + .HasColumnName("profileSkillsText"); + entity.Property(e => e.ProfileUrl) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("profileURL"); + entity.Property(e => e.ProfileWantToMask).HasColumnName("profileWantToMask"); + entity.Property(e => e.ProfileWantToText) + .IsRequired() + .HasColumnType("text") + .HasColumnName("profileWantToText"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Useruuid).HasName("PRIMARY"); + + entity + .ToTable("usersettings") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.Useruuid) + .HasMaxLength(36) + .HasColumnName("useruuid"); + entity.Property(e => e.Email) + .IsRequired() + .HasMaxLength(254) + .HasColumnName("email"); + entity.Property(e => e.Imviaemail) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("imviaemail"); + entity.Property(e => e.Visible) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("visible"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsGroup.cs b/Source/OpenSim.Data.Model/Core/OsGroupsGroup.cs new file mode 100644 index 00000000000..d514477c430 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsGroup.cs @@ -0,0 +1,31 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsGroup +{ + public string GroupId { get; set; } + + public string Location { get; set; } + + public string Name { get; set; } + + public string Charter { get; set; } + + public string InsigniaId { get; set; } + + public string FounderId { get; set; } + + public int MembershipFee { get; set; } + + public string OpenEnrollment { get; set; } + + public int ShowInList { get; set; } + + public int AllowPublish { get; set; } + + public int MaturePublish { get; set; } + + public string OwnerRoleId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsInvite.cs b/Source/OpenSim.Data.Model/Core/OsGroupsInvite.cs new file mode 100644 index 00000000000..45751022205 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsInvite.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsInvite +{ + public string InviteId { get; set; } + + public string GroupId { get; set; } + + public string RoleId { get; set; } + + public string PrincipalId { get; set; } + + public DateTime Tmstamp { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsMembership.cs b/Source/OpenSim.Data.Model/Core/OsGroupsMembership.cs new file mode 100644 index 00000000000..432ab150a83 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsMembership.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsMembership +{ + public string GroupId { get; set; } + + public string PrincipalId { get; set; } + + public string SelectedRoleId { get; set; } + + public int Contribution { get; set; } + + public int ListInProfile { get; set; } + + public int AcceptNotices { get; set; } + + public string AccessToken { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsNotice.cs b/Source/OpenSim.Data.Model/Core/OsGroupsNotice.cs new file mode 100644 index 00000000000..1b953967e5b --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsNotice.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsNotice +{ + public string GroupId { get; set; } + + public string NoticeId { get; set; } + + public uint Tmstamp { get; set; } + + public string FromName { get; set; } + + public string Subject { get; set; } + + public string Message { get; set; } + + public int HasAttachment { get; set; } + + public int AttachmentType { get; set; } + + public string AttachmentName { get; set; } + + public string AttachmentItemId { get; set; } + + public string AttachmentOwnerId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsPrincipal.cs b/Source/OpenSim.Data.Model/Core/OsGroupsPrincipal.cs new file mode 100644 index 00000000000..1b892e5d953 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsPrincipal.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsPrincipal +{ + public string PrincipalId { get; set; } + + public string ActiveGroupId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsRole.cs b/Source/OpenSim.Data.Model/Core/OsGroupsRole.cs new file mode 100644 index 00000000000..650d675e6df --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsRole.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsRole +{ + public string GroupId { get; set; } + + public string RoleId { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Title { get; set; } + + public ulong Powers { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/OsGroupsRolemembership.cs b/Source/OpenSim.Data.Model/Core/OsGroupsRolemembership.cs new file mode 100644 index 00000000000..c8c854d6640 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/OsGroupsRolemembership.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class OsGroupsRolemembership +{ + public string GroupId { get; set; } + + public string RoleId { get; set; } + + public string PrincipalId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Presence.cs b/Source/OpenSim.Data.Model/Core/Presence.cs new file mode 100644 index 00000000000..d90edb74fbf --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Presence.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Presence +{ + public string UserId { get; set; } + + public string RegionId { get; set; } + + public string SessionId { get; set; } + + public string SecureSessionId { get; set; } + + public DateTime LastSeen { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Region.cs b/Source/OpenSim.Data.Model/Core/Region.cs new file mode 100644 index 00000000000..1bdd3f7a51f --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Region.cs @@ -0,0 +1,81 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Region +{ + public string Uuid { get; set; } + + public ulong RegionHandle { get; set; } + + public string RegionName { get; set; } + + public string RegionRecvKey { get; set; } + + public string RegionSendKey { get; set; } + + public string RegionSecret { get; set; } + + public string RegionDataUri { get; set; } + + public string ServerIp { get; set; } + + public uint? ServerPort { get; set; } + + public string ServerUri { get; set; } + + public uint? LocX { get; set; } + + public uint? LocY { get; set; } + + public uint? LocZ { get; set; } + + public ulong? EastOverrideHandle { get; set; } + + public ulong? WestOverrideHandle { get; set; } + + public ulong? SouthOverrideHandle { get; set; } + + public ulong? NorthOverrideHandle { get; set; } + + public string RegionAssetUri { get; set; } + + public string RegionAssetRecvKey { get; set; } + + public string RegionAssetSendKey { get; set; } + + public string RegionUserUri { get; set; } + + public string RegionUserRecvKey { get; set; } + + public string RegionUserSendKey { get; set; } + + public string RegionMapTexture { get; set; } + + public int? ServerHttpPort { get; set; } + + public int? ServerRemotingPort { get; set; } + + public string OwnerUuid { get; set; } + + public string OriginUuid { get; set; } + + public uint? Access { get; set; } + + public string ScopeId { get; set; } + + public int SizeX { get; set; } + + public int SizeY { get; set; } + + public int Flags { get; set; } + + public int LastSeen { get; set; } + + public string PrincipalId { get; set; } + + public string Token { get; set; } + + public string ParcelMapTexture { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Token.cs b/Source/OpenSim.Data.Model/Core/Token.cs new file mode 100644 index 00000000000..c78f66d0a7e --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Token.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Token +{ + public string Uuid { get; set; } + + public string Token1 { get; set; } + + public DateTime Validity { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/UserAccount.cs b/Source/OpenSim.Data.Model/Core/UserAccount.cs new file mode 100644 index 00000000000..ba6798d2d1c --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/UserAccount.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class UserAccount +{ + public string PrincipalId { get; set; } + + public string ScopeId { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } + + public string Email { get; set; } + + public string ServiceUrls { get; set; } + + public int? Created { get; set; } + + public int UserLevel { get; set; } + + public int UserFlags { get; set; } + + public string UserTitle { get; set; } + + public int Active { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/UserAlias.cs b/Source/OpenSim.Data.Model/Core/UserAlias.cs new file mode 100644 index 00000000000..c0c5516029d --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/UserAlias.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class UserAlias +{ + public int Id { get; set; } + + public string AliasId { get; set; } + + public string UserId { get; set; } + + public string Description { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Userdatum.cs b/Source/OpenSim.Data.Model/Core/Userdatum.cs new file mode 100644 index 00000000000..094a03083c7 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Userdatum.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Userdatum +{ + public string UserId { get; set; } + + public string TagId { get; set; } + + public string DataKey { get; set; } + + public string DataVal { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Usernote.cs b/Source/OpenSim.Data.Model/Core/Usernote.cs new file mode 100644 index 00000000000..fee60695c1f --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Usernote.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Usernote +{ + public string Useruuid { get; set; } + + public string Targetuuid { get; set; } + + public string Notes { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Userpick.cs b/Source/OpenSim.Data.Model/Core/Userpick.cs new file mode 100644 index 00000000000..ab180c62e90 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Userpick.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Userpick +{ + public string Pickuuid { get; set; } + + public string Creatoruuid { get; set; } + + public string Toppick { get; set; } + + public string Parceluuid { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Snapshotuuid { get; set; } + + public string User { get; set; } + + public string Originalname { get; set; } + + public string Simname { get; set; } + + public string Posglobal { get; set; } + + public int Sortorder { get; set; } + + public string Enabled { get; set; } + + public string Gatekeeper { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Userprofile.cs b/Source/OpenSim.Data.Model/Core/Userprofile.cs new file mode 100644 index 00000000000..eacc0837d46 --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Userprofile.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Userprofile +{ + public string Useruuid { get; set; } + + public string ProfilePartner { get; set; } + + public byte[] ProfileAllowPublish { get; set; } + + public byte[] ProfileMaturePublish { get; set; } + + public string ProfileUrl { get; set; } + + public int ProfileWantToMask { get; set; } + + public string ProfileWantToText { get; set; } + + public int ProfileSkillsMask { get; set; } + + public string ProfileSkillsText { get; set; } + + public string ProfileLanguages { get; set; } + + public string ProfileImage { get; set; } + + public string ProfileAboutText { get; set; } + + public string ProfileFirstImage { get; set; } + + public string ProfileFirstText { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Core/Usersetting.cs b/Source/OpenSim.Data.Model/Core/Usersetting.cs new file mode 100644 index 00000000000..7885d44121b --- /dev/null +++ b/Source/OpenSim.Data.Model/Core/Usersetting.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Core; + +public partial class Usersetting +{ + public string Useruuid { get; set; } + + public string Imviaemail { get; set; } + + public string Visible { get; set; } + + public string Email { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Economy/Balance.cs b/Source/OpenSim.Data.Model/Economy/Balance.cs new file mode 100644 index 00000000000..a98db37e8a2 --- /dev/null +++ b/Source/OpenSim.Data.Model/Economy/Balance.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Economy; + +/// +/// Rev.4 +/// +public partial class Balance +{ + public string User { get; set; } + + public int Balance1 { get; set; } + + public sbyte? Status { get; set; } + + public sbyte Type { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Economy/OpenSimEconomyContext.cs b/Source/OpenSim.Data.Model/Economy/OpenSimEconomyContext.cs new file mode 100644 index 00000000000..ce817adc3b1 --- /dev/null +++ b/Source/OpenSim.Data.Model/Economy/OpenSimEconomyContext.cs @@ -0,0 +1,164 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace OpenSim.Data.Model.Economy; + +public partial class OpenSimEconomyContext : DbContext +{ + public OpenSimEconomyContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Balances { get; set; } + + public virtual DbSet Totalsales { get; set; } + + public virtual DbSet Transactions { get; set; } + + public virtual DbSet Userinfos { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder + .UseCollation("utf8mb4_0900_ai_ci") + .HasCharSet("utf8mb4"); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.User).HasName("PRIMARY"); + + entity + .ToTable("balances", tb => tb.HasComment("Rev.4")) + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.User) + .HasMaxLength(36) + .HasColumnName("user"); + entity.Property(e => e.Balance1).HasColumnName("balance"); + entity.Property(e => e.Status).HasColumnName("status"); + entity.Property(e => e.Type).HasColumnName("type"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("totalsales", tb => tb.HasComment("Rev.3")) + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Uuid) + .HasMaxLength(36) + .HasColumnName("UUID"); + entity.Property(e => e.ObjectUuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("objectUUID"); + entity.Property(e => e.Time).HasColumnName("time"); + entity.Property(e => e.Type).HasColumnName("type"); + entity.Property(e => e.User) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("user"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("transactions", tb => tb.HasComment("Rev.12")) + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Uuid) + .HasMaxLength(36) + .HasColumnName("UUID"); + entity.Property(e => e.Amount).HasColumnName("amount"); + entity.Property(e => e.CommonName) + .IsRequired() + .HasMaxLength(128) + .HasColumnName("commonName"); + entity.Property(e => e.Description) + .HasMaxLength(255) + .HasColumnName("description"); + entity.Property(e => e.ObjectName) + .HasMaxLength(255) + .HasColumnName("objectName"); + entity.Property(e => e.ObjectUuid) + .HasMaxLength(36) + .HasColumnName("objectUUID"); + entity.Property(e => e.Receiver) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("receiver"); + entity.Property(e => e.ReceiverBalance) + .HasDefaultValueSql("'-1'") + .HasColumnName("receiverBalance"); + entity.Property(e => e.RegionHandle) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("regionHandle"); + entity.Property(e => e.RegionUuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("regionUUID"); + entity.Property(e => e.Secure) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("secure"); + entity.Property(e => e.Sender) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("sender"); + entity.Property(e => e.SenderBalance) + .HasDefaultValueSql("'-1'") + .HasColumnName("senderBalance"); + entity.Property(e => e.Status).HasColumnName("status"); + entity.Property(e => e.Time).HasColumnName("time"); + entity.Property(e => e.Type).HasColumnName("type"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.User).HasName("PRIMARY"); + + entity + .ToTable("userinfo", tb => tb.HasComment("Rev.3")) + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.User) + .HasMaxLength(36) + .HasColumnName("user"); + entity.Property(e => e.Avatar) + .IsRequired() + .HasMaxLength(50) + .HasColumnName("avatar"); + entity.Property(e => e.Class).HasColumnName("class"); + entity.Property(e => e.Pass) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .HasColumnName("pass"); + entity.Property(e => e.Serverurl) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .HasColumnName("serverurl"); + entity.Property(e => e.Simip) + .IsRequired() + .HasMaxLength(64) + .HasColumnName("simip"); + entity.Property(e => e.Type).HasColumnName("type"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/Source/OpenSim.Data.Model/Economy/Totalsale.cs b/Source/OpenSim.Data.Model/Economy/Totalsale.cs new file mode 100644 index 00000000000..e7705a98eb9 --- /dev/null +++ b/Source/OpenSim.Data.Model/Economy/Totalsale.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Economy; + +/// +/// Rev.3 +/// +public partial class Totalsale +{ + public string Uuid { get; set; } + + public string User { get; set; } + + public string ObjectUuid { get; set; } + + public int Type { get; set; } + + public int TotalCount { get; set; } + + public int TotalAmount { get; set; } + + public int Time { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Economy/Transaction.cs b/Source/OpenSim.Data.Model/Economy/Transaction.cs new file mode 100644 index 00000000000..c0fb9e1d5b7 --- /dev/null +++ b/Source/OpenSim.Data.Model/Economy/Transaction.cs @@ -0,0 +1,42 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Economy; + +/// +/// Rev.12 +/// +public partial class Transaction +{ + public string Uuid { get; set; } + + public string Sender { get; set; } + + public string Receiver { get; set; } + + public int Amount { get; set; } + + public int SenderBalance { get; set; } + + public int ReceiverBalance { get; set; } + + public string ObjectUuid { get; set; } + + public string ObjectName { get; set; } + + public string RegionHandle { get; set; } + + public string RegionUuid { get; set; } + + public int Type { get; set; } + + public int Time { get; set; } + + public string Secure { get; set; } + + public bool Status { get; set; } + + public string CommonName { get; set; } + + public string Description { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Economy/Userinfo.cs b/Source/OpenSim.Data.Model/Economy/Userinfo.cs new file mode 100644 index 00000000000..c1c89207a59 --- /dev/null +++ b/Source/OpenSim.Data.Model/Economy/Userinfo.cs @@ -0,0 +1,24 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Economy; + +/// +/// Rev.3 +/// +public partial class Userinfo +{ + public string User { get; set; } + + public string Simip { get; set; } + + public string Avatar { get; set; } + + public string Pass { get; set; } + + public sbyte Type { get; set; } + + public sbyte Class { get; set; } + + public string Serverurl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Identity/EfmigrationsHistory.cs b/Source/OpenSim.Data.Model/Identity/EfmigrationsHistory.cs new file mode 100644 index 00000000000..6f2b6bce31d --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/EfmigrationsHistory.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class EfmigrationsHistory +{ + public string MigrationId { get; set; } + + public string ProductVersion { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityContext.cs b/Source/OpenSim.Data.Model/Identity/IdentityContext.cs new file mode 100644 index 00000000000..61903fab0ad --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityContext.cs @@ -0,0 +1,165 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityContext : DbContext +{ + public IdentityContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet EfmigrationsHistories { get; set; } + + public virtual DbSet IdentityRoles { get; set; } + + public virtual DbSet IdentityRoleClaims { get; set; } + + public virtual DbSet IdentityUsers { get; set; } + + public virtual DbSet IdentityUserClaims { get; set; } + + public virtual DbSet IdentityUserLogins { get; set; } + + public virtual DbSet IdentityUserTokens { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder + .UseCollation("utf8mb4_0900_ai_ci") + .HasCharSet("utf8mb4"); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.MigrationId).HasName("PRIMARY"); + + entity + .ToTable("__EFMigrationsHistory") + .UseCollation("utf8mb4_unicode_ci"); + + entity.Property(e => e.MigrationId).HasMaxLength(150); + entity.Property(e => e.ProductVersion) + .IsRequired() + .HasMaxLength(32); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity + .ToTable("Identity_Role") + .UseCollation("utf8mb4_unicode_ci"); + + entity.HasIndex(e => e.NormalizedName, "RoleNameIndex") + .IsUnique() + .HasAnnotation("MySql:IndexPrefixLength", new[] { 255 }); + + entity.Property(e => e.Name).HasMaxLength(256); + entity.Property(e => e.NormalizedName).HasMaxLength(256); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity + .ToTable("Identity_RoleClaims") + .UseCollation("utf8mb4_unicode_ci"); + + entity.HasIndex(e => e.RoleId, "IX_Identity_RoleClaims_RoleId"); + + entity.Property(e => e.RoleId).IsRequired(); + + entity.HasOne(d => d.Role).WithMany(p => p.IdentityRoleClaims).HasForeignKey(d => d.RoleId); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity + .ToTable("Identity_User") + .UseCollation("utf8mb4_unicode_ci"); + + entity.HasIndex(e => e.NormalizedEmail, "EmailIndex").HasAnnotation("MySql:IndexPrefixLength", new[] { 255 }); + + entity.HasIndex(e => e.NormalizedUserName, "UserNameIndex") + .IsUnique() + .HasAnnotation("MySql:IndexPrefixLength", new[] { 255 }); + + entity.Property(e => e.Email).HasMaxLength(256); + entity.Property(e => e.LockoutEnd).HasMaxLength(6); + entity.Property(e => e.NormalizedEmail).HasMaxLength(256); + entity.Property(e => e.NormalizedUserName).HasMaxLength(256); + entity.Property(e => e.UserName).HasMaxLength(256); + + entity.HasMany(d => d.Roles).WithMany(p => p.Users) + .UsingEntity>( + "IdentityUserRole", + r => r.HasOne().WithMany().HasForeignKey("RoleId"), + l => l.HasOne().WithMany().HasForeignKey("UserId"), + j => + { + j.HasKey("UserId", "RoleId") + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + j + .ToTable("Identity_UserRoles") + .UseCollation("utf8mb4_unicode_ci"); + j.HasIndex(new[] { "RoleId" }, "IX_Identity_UserRoles_RoleId"); + }); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PRIMARY"); + + entity + .ToTable("Identity_UserClaims") + .UseCollation("utf8mb4_unicode_ci"); + + entity.HasIndex(e => e.UserId, "IX_Identity_UserClaims_UserId"); + + entity.Property(e => e.UserId).IsRequired(); + + entity.HasOne(d => d.User).WithMany(p => p.IdentityUserClaims).HasForeignKey(d => d.UserId); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.LoginProvider, e.ProviderKey }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("Identity_UserLogins") + .UseCollation("utf8mb4_unicode_ci"); + + entity.HasIndex(e => e.UserId, "IX_Identity_UserLogins_UserId"); + + entity.Property(e => e.UserId).IsRequired(); + + entity.HasOne(d => d.User).WithMany(p => p.IdentityUserLogins).HasForeignKey(d => d.UserId); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.UserId, e.LoginProvider, e.Name }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0, 0 }); + + entity + .ToTable("Identity_UserTokens") + .UseCollation("utf8mb4_unicode_ci"); + + entity.HasOne(d => d.User).WithMany(p => p.IdentityUserTokens).HasForeignKey(d => d.UserId); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityRole.cs b/Source/OpenSim.Data.Model/Identity/IdentityRole.cs new file mode 100644 index 00000000000..0f0367d860a --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityRole.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityRole +{ + public string Id { get; set; } + + public string Name { get; set; } + + public string NormalizedName { get; set; } + + public string ConcurrencyStamp { get; set; } + + public virtual ICollection IdentityRoleClaims { get; set; } = new List(); + + public virtual ICollection Users { get; set; } = new List(); +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityRoleClaim.cs b/Source/OpenSim.Data.Model/Identity/IdentityRoleClaim.cs new file mode 100644 index 00000000000..64381413df3 --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityRoleClaim.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityRoleClaim +{ + public int Id { get; set; } + + public string RoleId { get; set; } + + public string ClaimType { get; set; } + + public string ClaimValue { get; set; } + + public virtual IdentityRole Role { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityUser.cs b/Source/OpenSim.Data.Model/Identity/IdentityUser.cs new file mode 100644 index 00000000000..253f8b9d54f --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityUser.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityUser +{ + public string Id { get; set; } + + public string FirstName { get; set; } + + public string LastName { get; set; } + + public byte[] ProfilePicture { get; set; } + + public string UserName { get; set; } + + public string NormalizedUserName { get; set; } + + public string Email { get; set; } + + public string NormalizedEmail { get; set; } + + public bool EmailConfirmed { get; set; } + + public string PasswordHash { get; set; } + + public string SecurityStamp { get; set; } + + public string ConcurrencyStamp { get; set; } + + public string PhoneNumber { get; set; } + + public bool PhoneNumberConfirmed { get; set; } + + public bool TwoFactorEnabled { get; set; } + + public DateTime? LockoutEnd { get; set; } + + public bool LockoutEnabled { get; set; } + + public int AccessFailedCount { get; set; } + + public virtual ICollection IdentityUserClaims { get; set; } = new List(); + + public virtual ICollection IdentityUserLogins { get; set; } = new List(); + + public virtual ICollection IdentityUserTokens { get; set; } = new List(); + + public virtual ICollection Roles { get; set; } = new List(); +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityUserClaim.cs b/Source/OpenSim.Data.Model/Identity/IdentityUserClaim.cs new file mode 100644 index 00000000000..0be8f9fd741 --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityUserClaim.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityUserClaim +{ + public int Id { get; set; } + + public string UserId { get; set; } + + public string ClaimType { get; set; } + + public string ClaimValue { get; set; } + + public virtual IdentityUser User { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityUserLogin.cs b/Source/OpenSim.Data.Model/Identity/IdentityUserLogin.cs new file mode 100644 index 00000000000..4b515f29a46 --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityUserLogin.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityUserLogin +{ + public string LoginProvider { get; set; } + + public string ProviderKey { get; set; } + + public string ProviderDisplayName { get; set; } + + public string UserId { get; set; } + + public virtual IdentityUser User { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Identity/IdentityUserToken.cs b/Source/OpenSim.Data.Model/Identity/IdentityUserToken.cs new file mode 100644 index 00000000000..0128cc240e7 --- /dev/null +++ b/Source/OpenSim.Data.Model/Identity/IdentityUserToken.cs @@ -0,0 +1,17 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Identity; + +public partial class IdentityUserToken +{ + public string UserId { get; set; } + + public string LoginProvider { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } + + public virtual IdentityUser User { get; set; } +} diff --git a/Source/OpenSim.Data.Model/OpenSim.Data.Model.csproj b/Source/OpenSim.Data.Model/OpenSim.Data.Model.csproj new file mode 100644 index 00000000000..f5a4d22aba4 --- /dev/null +++ b/Source/OpenSim.Data.Model/OpenSim.Data.Model.csproj @@ -0,0 +1,30 @@ + + + + net8.0 + 5af6ba03-92b0-40d9-b129-45a5c6788134 + OpenSim.Data.Model + OpenSim.Data.Model + Copyright (c) Utopia Skye, LLC 2024 + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/Source/OpenSim.Data.Model/Region/Bakedterrain.cs b/Source/OpenSim.Data.Model/Region/Bakedterrain.cs new file mode 100644 index 00000000000..1c6effcc640 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Bakedterrain.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Bakedterrain +{ + public string RegionUuid { get; set; } + + public int? Revision { get; set; } + + public byte[] Heightfield { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Land.cs b/Source/OpenSim.Data.Model/Region/Land.cs new file mode 100644 index 00000000000..edb1ee234fe --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Land.cs @@ -0,0 +1,95 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Land +{ + public string Uuid { get; set; } + + public string RegionUuid { get; set; } + + public int? LocalLandId { get; set; } + + public byte[] Bitmap { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string OwnerUuid { get; set; } + + public int? IsGroupOwned { get; set; } + + public int? Area { get; set; } + + public int? AuctionId { get; set; } + + public int? Category { get; set; } + + public int? ClaimDate { get; set; } + + public int? ClaimPrice { get; set; } + + public string GroupUuid { get; set; } + + public int? SalePrice { get; set; } + + public int? LandStatus { get; set; } + + public uint? LandFlags { get; set; } + + public int? LandingType { get; set; } + + public int? MediaAutoScale { get; set; } + + public string MediaTextureUuid { get; set; } + + public string MediaUrl { get; set; } + + public string MusicUrl { get; set; } + + public float? PassHours { get; set; } + + public int? PassPrice { get; set; } + + public string SnapshotUuid { get; set; } + + public float? UserLocationX { get; set; } + + public float? UserLocationY { get; set; } + + public float? UserLocationZ { get; set; } + + public float? UserLookAtX { get; set; } + + public float? UserLookAtY { get; set; } + + public float? UserLookAtZ { get; set; } + + public string AuthbuyerId { get; set; } + + public int OtherCleanTime { get; set; } + + public int Dwell { get; set; } + + public string MediaType { get; set; } + + public string MediaDescription { get; set; } + + public string MediaSize { get; set; } + + public bool MediaLoop { get; set; } + + public bool ObscureMusic { get; set; } + + public bool ObscureMedia { get; set; } + + public sbyte SeeAvs { get; set; } + + public sbyte AnyAvsounds { get; set; } + + public sbyte GroupAvsounds { get; set; } + + public string Environment { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Landaccesslist.cs b/Source/OpenSim.Data.Model/Region/Landaccesslist.cs new file mode 100644 index 00000000000..ca1fb50ebee --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Landaccesslist.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Landaccesslist +{ + public string LandUuid { get; set; } + + public string AccessUuid { get; set; } + + public int? Flags { get; set; } + + public int Expires { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Migration.cs b/Source/OpenSim.Data.Model/Region/Migration.cs new file mode 100644 index 00000000000..1db873c166d --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Migration.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Migration +{ + public string Name { get; set; } + + public int? Version { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/OpenSimRegionContext.cs b/Source/OpenSim.Data.Model/Region/OpenSimRegionContext.cs new file mode 100644 index 00000000000..2fe5492d97b --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/OpenSimRegionContext.cs @@ -0,0 +1,863 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace OpenSim.Data.Model.Region; + +public partial class OpenSimRegionContext : DbContext +{ + public OpenSimRegionContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Bakedterrains { get; set; } + + public virtual DbSet Lands { get; set; } + + public virtual DbSet Landaccesslists { get; set; } + + public virtual DbSet Migrations { get; set; } + + public virtual DbSet Prims { get; set; } + + public virtual DbSet Primitems { get; set; } + + public virtual DbSet Primshapes { get; set; } + + public virtual DbSet Regionbans { get; set; } + + public virtual DbSet Regionenvironments { get; set; } + + public virtual DbSet Regionextras { get; set; } + + public virtual DbSet Regionsettings { get; set; } + + public virtual DbSet Regionwindlights { get; set; } + + public virtual DbSet SpawnPoints { get; set; } + + public virtual DbSet Terrains { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder + .UseCollation("utf8mb4_0900_ai_ci") + .HasCharSet("utf8mb4"); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("bakedterrain") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.RegionUuid) + .HasMaxLength(255) + .HasColumnName("RegionUUID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("land") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Uuid).HasColumnName("UUID"); + entity.Property(e => e.AnyAvsounds) + .HasDefaultValueSql("'1'") + .HasColumnName("AnyAVSounds"); + entity.Property(e => e.AuctionId).HasColumnName("AuctionID"); + entity.Property(e => e.AuthbuyerId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .HasColumnName("AuthbuyerID"); + entity.Property(e => e.Description).HasMaxLength(255); + entity.Property(e => e.Environment) + .HasColumnType("mediumtext") + .HasColumnName("environment"); + entity.Property(e => e.GroupAvsounds) + .HasDefaultValueSql("'1'") + .HasColumnName("GroupAVSounds"); + entity.Property(e => e.GroupUuid) + .HasMaxLength(255) + .HasColumnName("GroupUUID"); + entity.Property(e => e.LocalLandId).HasColumnName("LocalLandID"); + entity.Property(e => e.MediaDescription) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''"); + entity.Property(e => e.MediaSize) + .IsRequired() + .HasMaxLength(16) + .HasDefaultValueSql("'0,0'"); + entity.Property(e => e.MediaTextureUuid) + .HasMaxLength(255) + .HasColumnName("MediaTextureUUID"); + entity.Property(e => e.MediaType) + .IsRequired() + .HasMaxLength(32) + .HasDefaultValueSql("'none/none'"); + entity.Property(e => e.MediaUrl) + .HasMaxLength(255) + .HasColumnName("MediaURL"); + entity.Property(e => e.MusicUrl) + .HasMaxLength(255) + .HasColumnName("MusicURL"); + entity.Property(e => e.Name).HasMaxLength(255); + entity.Property(e => e.OwnerUuid) + .HasMaxLength(255) + .HasColumnName("OwnerUUID"); + entity.Property(e => e.RegionUuid) + .HasMaxLength(255) + .HasColumnName("RegionUUID"); + entity.Property(e => e.SeeAvs) + .HasDefaultValueSql("'1'") + .HasColumnName("SeeAVs"); + entity.Property(e => e.SnapshotUuid) + .HasMaxLength(255) + .HasColumnName("SnapshotUUID"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("landaccesslist") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.AccessUuid) + .HasMaxLength(255) + .HasColumnName("AccessUUID"); + entity.Property(e => e.LandUuid) + .HasMaxLength(255) + .HasColumnName("LandUUID"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("migrations") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Name) + .HasMaxLength(100) + .HasColumnName("name"); + entity.Property(e => e.Version).HasColumnName("version"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("prims") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.RegionUuid, "prims_regionuuid"); + + entity.HasIndex(e => e.SceneGroupId, "prims_scenegroupid"); + + entity.Property(e => e.Uuid) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("UUID"); + entity.Property(e => e.AccelerationX).HasDefaultValueSql("'0'"); + entity.Property(e => e.AccelerationY).HasDefaultValueSql("'0'"); + entity.Property(e => e.AccelerationZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.AngularVelocityX).HasDefaultValueSql("'0'"); + entity.Property(e => e.AngularVelocityY).HasDefaultValueSql("'0'"); + entity.Property(e => e.AngularVelocityZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.AttachedPosX).HasDefaultValueSql("'0'"); + entity.Property(e => e.AttachedPosY).HasDefaultValueSql("'0'"); + entity.Property(e => e.AttachedPosZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.CameraAtOffsetX).HasDefaultValueSql("'0'"); + entity.Property(e => e.CameraAtOffsetY).HasDefaultValueSql("'0'"); + entity.Property(e => e.CameraAtOffsetZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.CameraEyeOffsetX).HasDefaultValueSql("'0'"); + entity.Property(e => e.CameraEyeOffsetY).HasDefaultValueSql("'0'"); + entity.Property(e => e.CameraEyeOffsetZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.CollisionSound) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength(); + entity.Property(e => e.CreatorId) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .HasColumnName("CreatorID"); + entity.Property(e => e.Density).HasDefaultValueSql("'1000'"); + entity.Property(e => e.Description) + .HasMaxLength(255) + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.DynAttrs).HasColumnType("text"); + entity.Property(e => e.Friction).HasDefaultValueSql("'0.6'"); + entity.Property(e => e.GravityModifier).HasDefaultValueSql("'1'"); + entity.Property(e => e.GroupId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("GroupID"); + entity.Property(e => e.GroupPositionX).HasDefaultValueSql("'0'"); + entity.Property(e => e.GroupPositionY).HasDefaultValueSql("'0'"); + entity.Property(e => e.GroupPositionZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.KeyframeMotion).HasColumnType("blob"); + entity.Property(e => e.LastOwnerId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("LastOwnerID"); + entity.Property(e => e.Linksetdata) + .HasColumnType("mediumtext") + .HasColumnName("linksetdata"); + entity.Property(e => e.LoopedSound) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength(); + entity.Property(e => e.LoopedSoundGain).HasDefaultValueSql("'0'"); + entity.Property(e => e.Material).HasDefaultValueSql("'3'"); + entity.Property(e => e.MediaUrl) + .HasMaxLength(255) + .HasColumnName("MediaURL"); + entity.Property(e => e.Name) + .HasMaxLength(255) + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.OmegaX).HasDefaultValueSql("'0'"); + entity.Property(e => e.OmegaY).HasDefaultValueSql("'0'"); + entity.Property(e => e.OmegaZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.OwnerId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("OwnerID"); + entity.Property(e => e.ParticleSystem).HasColumnType("blob"); + entity.Property(e => e.PhysInertia).HasColumnType("text"); + entity.Property(e => e.PositionX).HasDefaultValueSql("'0'"); + entity.Property(e => e.PositionY).HasDefaultValueSql("'0'"); + entity.Property(e => e.PositionZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.Pseudocrc) + .HasDefaultValueSql("'0'") + .HasColumnName("pseudocrc"); + entity.Property(e => e.RegionUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("RegionUUID"); + entity.Property(e => e.Restitution).HasDefaultValueSql("'0.5'"); + entity.Property(e => e.RezzerId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("RezzerID"); + entity.Property(e => e.RotationW).HasDefaultValueSql("'0'"); + entity.Property(e => e.RotationX).HasDefaultValueSql("'0'"); + entity.Property(e => e.RotationY).HasDefaultValueSql("'0'"); + entity.Property(e => e.RotationZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.SalePrice).HasDefaultValueSql("'10'"); + entity.Property(e => e.SceneGroupId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("SceneGroupID"); + entity.Property(e => e.SitName) + .HasMaxLength(255) + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.SitTargetOffsetX).HasDefaultValueSql("'0'"); + entity.Property(e => e.SitTargetOffsetY).HasDefaultValueSql("'0'"); + entity.Property(e => e.SitTargetOffsetZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.SitTargetOrientW).HasDefaultValueSql("'0'"); + entity.Property(e => e.SitTargetOrientX).HasDefaultValueSql("'0'"); + entity.Property(e => e.SitTargetOrientY).HasDefaultValueSql("'0'"); + entity.Property(e => e.SitTargetOrientZ).HasDefaultValueSql("'0'"); + entity.Property(e => e.Sitactrange) + .HasDefaultValueSql("'0'") + .HasColumnName("sitactrange"); + entity.Property(e => e.Sopanims) + .HasColumnType("blob") + .HasColumnName("sopanims"); + entity.Property(e => e.Standtargetx) + .HasDefaultValueSql("'0'") + .HasColumnName("standtargetx"); + entity.Property(e => e.Standtargety) + .HasDefaultValueSql("'0'") + .HasColumnName("standtargety"); + entity.Property(e => e.Standtargetz) + .HasDefaultValueSql("'0'") + .HasColumnName("standtargetz"); + entity.Property(e => e.Text) + .HasMaxLength(255) + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.TextureAnimation).HasColumnType("blob"); + entity.Property(e => e.TouchName) + .HasMaxLength(255) + .UseCollation("utf8mb3_general_ci") + .HasCharSet("utf8mb3"); + entity.Property(e => e.Vehicle).HasColumnType("text"); + entity.Property(e => e.VelocityX).HasDefaultValueSql("'0'"); + entity.Property(e => e.VelocityY).HasDefaultValueSql("'0'"); + entity.Property(e => e.VelocityZ).HasDefaultValueSql("'0'"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ItemId).HasName("PRIMARY"); + + entity + .ToTable("primitems") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.HasIndex(e => e.PrimId, "primitems_primid"); + + entity.Property(e => e.ItemId) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("itemID"); + entity.Property(e => e.AssetId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("assetID"); + entity.Property(e => e.AssetType).HasColumnName("assetType"); + entity.Property(e => e.BasePermissions).HasColumnName("basePermissions"); + entity.Property(e => e.CreationDate).HasColumnName("creationDate"); + entity.Property(e => e.CreatorId) + .IsRequired() + .HasMaxLength(255) + .HasDefaultValueSql("''") + .HasColumnName("CreatorID"); + entity.Property(e => e.CurrentPermissions).HasColumnName("currentPermissions"); + entity.Property(e => e.Description) + .HasMaxLength(255) + .HasColumnName("description"); + entity.Property(e => e.EveryonePermissions).HasColumnName("everyonePermissions"); + entity.Property(e => e.Flags).HasColumnName("flags"); + entity.Property(e => e.GroupId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("groupID"); + entity.Property(e => e.GroupPermissions).HasColumnName("groupPermissions"); + entity.Property(e => e.InvType).HasColumnName("invType"); + entity.Property(e => e.LastOwnerId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("lastOwnerID"); + entity.Property(e => e.Name) + .HasMaxLength(255) + .HasColumnName("name"); + entity.Property(e => e.NextPermissions).HasColumnName("nextPermissions"); + entity.Property(e => e.OwnerId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("ownerID"); + entity.Property(e => e.ParentFolderId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parentFolderID"); + entity.Property(e => e.PrimId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("primID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Uuid).HasName("PRIMARY"); + + entity + .ToTable("primshapes") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.Uuid) + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("UUID"); + entity.Property(e => e.MatOvrd).HasColumnType("blob"); + entity.Property(e => e.Media).HasColumnType("text"); + entity.Property(e => e.Pcode).HasColumnName("PCode"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("regionban") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.BannedIp) + .IsRequired() + .HasMaxLength(16) + .HasColumnName("bannedIp"); + entity.Property(e => e.BannedIpHostMask) + .IsRequired() + .HasMaxLength(16) + .HasColumnName("bannedIpHostMask"); + entity.Property(e => e.BannedUuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("bannedUUID"); + entity.Property(e => e.RegionUuid) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("regionUUID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.RegionId).HasName("PRIMARY"); + + entity + .ToTable("regionenvironment") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.RegionId) + .HasMaxLength(36) + .HasColumnName("region_id"); + entity.Property(e => e.LlsdSettings) + .HasColumnType("mediumtext") + .HasColumnName("llsd_settings"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.RegionId, e.Name }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("regionextra") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.RegionId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("RegionID"); + entity.Property(e => e.Name).HasMaxLength(32); + entity.Property(e => e.Value) + .HasColumnType("text") + .HasColumnName("value"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.RegionUuid).HasName("PRIMARY"); + + entity + .ToTable("regionsettings") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.RegionUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("regionUUID"); + entity.Property(e => e.AgentLimit).HasColumnName("agent_limit"); + entity.Property(e => e.AllowDamage).HasColumnName("allow_damage"); + entity.Property(e => e.AllowLandJoinDivide).HasColumnName("allow_land_join_divide"); + entity.Property(e => e.AllowLandResell).HasColumnName("allow_land_resell"); + entity.Property(e => e.BlockFly).HasColumnName("block_fly"); + entity.Property(e => e.BlockSearch).HasColumnName("block_search"); + entity.Property(e => e.BlockShowInSearch).HasColumnName("block_show_in_search"); + entity.Property(e => e.BlockTerraform).HasColumnName("block_terraform"); + entity.Property(e => e.CacheId) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("cacheID"); + entity.Property(e => e.Casino).HasColumnName("casino"); + entity.Property(e => e.Covenant) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("covenant"); + entity.Property(e => e.CovenantDatetime).HasColumnName("covenant_datetime"); + entity.Property(e => e.DisableCollisions).HasColumnName("disable_collisions"); + entity.Property(e => e.DisablePhysics).HasColumnName("disable_physics"); + entity.Property(e => e.DisableScripts).HasColumnName("disable_scripts"); + entity.Property(e => e.Elevation1Ne).HasColumnName("elevation_1_ne"); + entity.Property(e => e.Elevation1Nw).HasColumnName("elevation_1_nw"); + entity.Property(e => e.Elevation1Se).HasColumnName("elevation_1_se"); + entity.Property(e => e.Elevation1Sw).HasColumnName("elevation_1_sw"); + entity.Property(e => e.Elevation2Ne).HasColumnName("elevation_2_ne"); + entity.Property(e => e.Elevation2Nw).HasColumnName("elevation_2_nw"); + entity.Property(e => e.Elevation2Se).HasColumnName("elevation_2_se"); + entity.Property(e => e.Elevation2Sw).HasColumnName("elevation_2_sw"); + entity.Property(e => e.FixedSun).HasColumnName("fixed_sun"); + entity.Property(e => e.LoadedCreationDatetime).HasColumnName("loaded_creation_datetime"); + entity.Property(e => e.LoadedCreationId) + .HasMaxLength(64) + .HasColumnName("loaded_creation_id"); + entity.Property(e => e.MapTileId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("map_tile_ID"); + entity.Property(e => e.Maturity).HasColumnName("maturity"); + entity.Property(e => e.ObjectBonus).HasColumnName("object_bonus"); + entity.Property(e => e.ParcelTileId) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("parcel_tile_ID"); + entity.Property(e => e.RestrictPushing).HasColumnName("restrict_pushing"); + entity.Property(e => e.SunPosition).HasColumnName("sun_position"); + entity.Property(e => e.Sunvectorx).HasColumnName("sunvectorx"); + entity.Property(e => e.Sunvectory).HasColumnName("sunvectory"); + entity.Property(e => e.Sunvectorz).HasColumnName("sunvectorz"); + entity.Property(e => e.TelehubObject) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'"); + entity.Property(e => e.TerrainLowerLimit).HasColumnName("terrain_lower_limit"); + entity.Property(e => e.TerrainRaiseLimit).HasColumnName("terrain_raise_limit"); + entity.Property(e => e.TerrainTexture1) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("terrain_texture_1"); + entity.Property(e => e.TerrainTexture2) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("terrain_texture_2"); + entity.Property(e => e.TerrainTexture3) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("terrain_texture_3"); + entity.Property(e => e.TerrainTexture4) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("terrain_texture_4"); + entity.Property(e => e.UseEstateSun).HasColumnName("use_estate_sun"); + entity.Property(e => e.WaterHeight).HasColumnName("water_height"); + entity.Property(e => e.TerrainPBR1) + .HasColumnName("TerrainPBR1") + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'"); + entity.Property(e => e.TerrainPBR2) + .HasColumnName("TerrainPBR2") + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'"); + entity.Property(e => e.TerrainPBR3) + .HasColumnName("TerrainPBR3") + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'"); + entity.Property(e => e.TerrainPBR4) + .HasColumnName("TerrainPBR4") + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.RegionId).HasName("PRIMARY"); + + entity + .ToTable("regionwindlight") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.RegionId) + .HasMaxLength(36) + .HasDefaultValueSql("'000000-0000-0000-0000-000000000000'") + .HasColumnName("region_id"); + entity.Property(e => e.AmbientB) + .HasDefaultValueSql("'0.34999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("ambient_b"); + entity.Property(e => e.AmbientG) + .HasDefaultValueSql("'0.34999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("ambient_g"); + entity.Property(e => e.AmbientI) + .HasDefaultValueSql("'0.34999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("ambient_i"); + entity.Property(e => e.AmbientR) + .HasDefaultValueSql("'0.34999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("ambient_r"); + entity.Property(e => e.BigWaveDirectionX) + .HasDefaultValueSql("'1.04999995'") + .HasColumnType("float(9,8)") + .HasColumnName("big_wave_direction_x"); + entity.Property(e => e.BigWaveDirectionY) + .HasDefaultValueSql("'-0.41999999'") + .HasColumnType("float(9,8)") + .HasColumnName("big_wave_direction_y"); + entity.Property(e => e.BlueDensityB) + .HasDefaultValueSql("'0.38000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("blue_density_b"); + entity.Property(e => e.BlueDensityG) + .HasDefaultValueSql("'0.22000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("blue_density_g"); + entity.Property(e => e.BlueDensityI) + .HasDefaultValueSql("'0.38000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("blue_density_i"); + entity.Property(e => e.BlueDensityR) + .HasDefaultValueSql("'0.12000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("blue_density_r"); + entity.Property(e => e.BlurMultiplier) + .HasDefaultValueSql("'0.04000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("blur_multiplier"); + entity.Property(e => e.CloudColorB) + .HasDefaultValueSql("'0.41000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_color_b"); + entity.Property(e => e.CloudColorG) + .HasDefaultValueSql("'0.41000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_color_g"); + entity.Property(e => e.CloudColorI) + .HasDefaultValueSql("'0.41000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_color_i"); + entity.Property(e => e.CloudColorR) + .HasDefaultValueSql("'0.41000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_color_r"); + entity.Property(e => e.CloudCoverage) + .HasDefaultValueSql("'0.27000001'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_coverage"); + entity.Property(e => e.CloudDensity) + .HasDefaultValueSql("'1.00000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_density"); + entity.Property(e => e.CloudDetailDensity) + .HasDefaultValueSql("'0.12000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_detail_density"); + entity.Property(e => e.CloudDetailX) + .HasDefaultValueSql("'1.00000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_detail_x"); + entity.Property(e => e.CloudDetailY) + .HasDefaultValueSql("'0.52999997'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_detail_y"); + entity.Property(e => e.CloudScale) + .HasDefaultValueSql("'0.41999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_scale"); + entity.Property(e => e.CloudScrollX) + .HasDefaultValueSql("'0.2000000'") + .HasColumnType("float(9,7)") + .HasColumnName("cloud_scroll_x"); + entity.Property(e => e.CloudScrollXLock).HasColumnName("cloud_scroll_x_lock"); + entity.Property(e => e.CloudScrollY) + .HasDefaultValueSql("'0.0100000'") + .HasColumnType("float(9,7)") + .HasColumnName("cloud_scroll_y"); + entity.Property(e => e.CloudScrollYLock).HasColumnName("cloud_scroll_y_lock"); + entity.Property(e => e.CloudX) + .HasDefaultValueSql("'1.00000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_x"); + entity.Property(e => e.CloudY) + .HasDefaultValueSql("'0.52999997'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("cloud_y"); + entity.Property(e => e.DensityMultiplier) + .HasDefaultValueSql("'0.18000001'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("density_multiplier"); + entity.Property(e => e.DistanceMultiplier) + .HasDefaultValueSql("'0.800000'") + .HasColumnType("float(9,6) unsigned") + .HasColumnName("distance_multiplier"); + entity.Property(e => e.DrawClassicClouds) + .HasDefaultValueSql("'1'") + .HasColumnName("draw_classic_clouds"); + entity.Property(e => e.EastAngle) + .HasColumnType("float(9,8) unsigned") + .HasColumnName("east_angle"); + entity.Property(e => e.FresnelOffset) + .HasDefaultValueSql("'0.50000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("fresnel_offset"); + entity.Property(e => e.FresnelScale) + .HasDefaultValueSql("'0.40000001'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("fresnel_scale"); + entity.Property(e => e.HazeDensity) + .HasDefaultValueSql("'0.69999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("haze_density"); + entity.Property(e => e.HazeHorizon) + .HasDefaultValueSql("'0.19000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("haze_horizon"); + entity.Property(e => e.HorizonB) + .HasDefaultValueSql("'0.31999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("horizon_b"); + entity.Property(e => e.HorizonG) + .HasDefaultValueSql("'0.25000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("horizon_g"); + entity.Property(e => e.HorizonI) + .HasDefaultValueSql("'0.31999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("horizon_i"); + entity.Property(e => e.HorizonR) + .HasDefaultValueSql("'0.25000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("horizon_r"); + entity.Property(e => e.LittleWaveDirectionX) + .HasDefaultValueSql("'1.11000001'") + .HasColumnType("float(9,8)") + .HasColumnName("little_wave_direction_x"); + entity.Property(e => e.LittleWaveDirectionY) + .HasDefaultValueSql("'-1.15999997'") + .HasColumnType("float(9,8)") + .HasColumnName("little_wave_direction_y"); + entity.Property(e => e.MaxAltitude) + .HasDefaultValueSql("'1605'") + .HasColumnName("max_altitude"); + entity.Property(e => e.NormalMapTexture) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'822ded49-9a6c-f61c-cb89-6df54f42cdf4'") + .HasColumnName("normal_map_texture"); + entity.Property(e => e.ReflectionWaveletScale1) + .HasDefaultValueSql("'2.0000000'") + .HasColumnType("float(9,7) unsigned") + .HasColumnName("reflection_wavelet_scale_1"); + entity.Property(e => e.ReflectionWaveletScale2) + .HasDefaultValueSql("'2.0000000'") + .HasColumnType("float(9,7) unsigned") + .HasColumnName("reflection_wavelet_scale_2"); + entity.Property(e => e.ReflectionWaveletScale3) + .HasDefaultValueSql("'2.0000000'") + .HasColumnType("float(9,7) unsigned") + .HasColumnName("reflection_wavelet_scale_3"); + entity.Property(e => e.RefractScaleAbove) + .HasDefaultValueSql("'0.03000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("refract_scale_above"); + entity.Property(e => e.RefractScaleBelow) + .HasDefaultValueSql("'0.20000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("refract_scale_below"); + entity.Property(e => e.SceneGamma) + .HasDefaultValueSql("'1.0000000'") + .HasColumnType("float(9,7) unsigned") + .HasColumnName("scene_gamma"); + entity.Property(e => e.StarBrightness) + .HasColumnType("float(9,8) unsigned") + .HasColumnName("star_brightness"); + entity.Property(e => e.SunGlowFocus) + .HasDefaultValueSql("'0.10000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_glow_focus"); + entity.Property(e => e.SunGlowSize) + .HasDefaultValueSql("'1.75000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_glow_size"); + entity.Property(e => e.SunMoonColorB) + .HasDefaultValueSql("'0.30000001'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_moon_color_b"); + entity.Property(e => e.SunMoonColorG) + .HasDefaultValueSql("'0.25999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_moon_color_g"); + entity.Property(e => e.SunMoonColorI) + .HasDefaultValueSql("'0.30000001'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_moon_color_i"); + entity.Property(e => e.SunMoonColorR) + .HasDefaultValueSql("'0.23999999'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_moon_color_r"); + entity.Property(e => e.SunMoonPosition) + .HasDefaultValueSql("'0.31700000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("sun_moon_position"); + entity.Property(e => e.UnderwaterFogModifier) + .HasDefaultValueSql("'0.25000000'") + .HasColumnType("float(9,8) unsigned") + .HasColumnName("underwater_fog_modifier"); + entity.Property(e => e.WaterColorB) + .HasDefaultValueSql("'64.000000'") + .HasColumnType("float(9,6) unsigned") + .HasColumnName("water_color_b"); + entity.Property(e => e.WaterColorG) + .HasDefaultValueSql("'38.000000'") + .HasColumnType("float(9,6) unsigned") + .HasColumnName("water_color_g"); + entity.Property(e => e.WaterColorR) + .HasDefaultValueSql("'4.000000'") + .HasColumnType("float(9,6) unsigned") + .HasColumnName("water_color_r"); + entity.Property(e => e.WaterFogDensityExponent) + .HasDefaultValueSql("'4.0000000'") + .HasColumnType("float(9,7) unsigned") + .HasColumnName("water_fog_density_exponent"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("spawn_points") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.HasIndex(e => e.RegionId, "RegionID"); + + entity.Property(e => e.RegionId) + .IsRequired() + .HasMaxLength(36) + .HasColumnName("RegionID"); + }); + + modelBuilder.Entity(entity => + { + entity + .HasNoKey() + .ToTable("terrain") + .HasCharSet("latin1") + .UseCollation("latin1_swedish_ci"); + + entity.Property(e => e.RegionUuid) + .HasMaxLength(255) + .HasColumnName("RegionUUID"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/Source/OpenSim.Data.Model/Region/Prim.cs b/Source/OpenSim.Data.Model/Region/Prim.cs new file mode 100644 index 00000000000..ebed04dfce0 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Prim.cs @@ -0,0 +1,211 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Prim +{ + public int? CreationDate { get; set; } + + public string Name { get; set; } + + public string Text { get; set; } + + public string Description { get; set; } + + public string SitName { get; set; } + + public string TouchName { get; set; } + + public int? ObjectFlags { get; set; } + + public int? OwnerMask { get; set; } + + public int? NextOwnerMask { get; set; } + + public int? GroupMask { get; set; } + + public int? EveryoneMask { get; set; } + + public int? BaseMask { get; set; } + + public float? PositionX { get; set; } + + public float? PositionY { get; set; } + + public float? PositionZ { get; set; } + + public float? GroupPositionX { get; set; } + + public float? GroupPositionY { get; set; } + + public float? GroupPositionZ { get; set; } + + public float? VelocityX { get; set; } + + public float? VelocityY { get; set; } + + public float? VelocityZ { get; set; } + + public float? AngularVelocityX { get; set; } + + public float? AngularVelocityY { get; set; } + + public float? AngularVelocityZ { get; set; } + + public float? AccelerationX { get; set; } + + public float? AccelerationY { get; set; } + + public float? AccelerationZ { get; set; } + + public float? RotationX { get; set; } + + public float? RotationY { get; set; } + + public float? RotationZ { get; set; } + + public float? RotationW { get; set; } + + public float? SitTargetOffsetX { get; set; } + + public float? SitTargetOffsetY { get; set; } + + public float? SitTargetOffsetZ { get; set; } + + public float? SitTargetOrientW { get; set; } + + public float? SitTargetOrientX { get; set; } + + public float? SitTargetOrientY { get; set; } + + public float? SitTargetOrientZ { get; set; } + + public string Uuid { get; set; } + + public string RegionUuid { get; set; } + + public string CreatorId { get; set; } + + public string OwnerId { get; set; } + + public string GroupId { get; set; } + + public string LastOwnerId { get; set; } + + public string SceneGroupId { get; set; } + + public int PayPrice { get; set; } + + public int PayButton1 { get; set; } + + public int PayButton2 { get; set; } + + public int PayButton3 { get; set; } + + public int PayButton4 { get; set; } + + public string LoopedSound { get; set; } + + public float? LoopedSoundGain { get; set; } + + public byte[] TextureAnimation { get; set; } + + public float? OmegaX { get; set; } + + public float? OmegaY { get; set; } + + public float? OmegaZ { get; set; } + + public float? CameraEyeOffsetX { get; set; } + + public float? CameraEyeOffsetY { get; set; } + + public float? CameraEyeOffsetZ { get; set; } + + public float? CameraAtOffsetX { get; set; } + + public float? CameraAtOffsetY { get; set; } + + public float? CameraAtOffsetZ { get; set; } + + public sbyte ForceMouselook { get; set; } + + public int ScriptAccessPin { get; set; } + + public sbyte AllowedDrop { get; set; } + + public sbyte DieAtEdge { get; set; } + + public int SalePrice { get; set; } + + public sbyte SaleType { get; set; } + + public int ColorR { get; set; } + + public int ColorG { get; set; } + + public int ColorB { get; set; } + + public int ColorA { get; set; } + + public byte[] ParticleSystem { get; set; } + + public sbyte ClickAction { get; set; } + + public sbyte Material { get; set; } + + public string CollisionSound { get; set; } + + public double CollisionSoundVolume { get; set; } + + public int LinkNumber { get; set; } + + public sbyte PassTouches { get; set; } + + public string MediaUrl { get; set; } + + public string DynAttrs { get; set; } + + public sbyte PhysicsShapeType { get; set; } + + public float? Density { get; set; } + + public float? GravityModifier { get; set; } + + public float? Friction { get; set; } + + public float? Restitution { get; set; } + + public byte[] KeyframeMotion { get; set; } + + public float? AttachedPosX { get; set; } + + public float? AttachedPosY { get; set; } + + public float? AttachedPosZ { get; set; } + + public sbyte PassCollisions { get; set; } + + public string Vehicle { get; set; } + + public sbyte RotationAxisLocks { get; set; } + + public string RezzerId { get; set; } + + public string PhysInertia { get; set; } + + public byte[] Sopanims { get; set; } + + public float? Standtargetx { get; set; } + + public float? Standtargety { get; set; } + + public float? Standtargetz { get; set; } + + public float? Sitactrange { get; set; } + + public int? Pseudocrc { get; set; } + + public string Linksetdata { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Primitem.cs b/Source/OpenSim.Data.Model/Region/Primitem.cs new file mode 100644 index 00000000000..6be06509cbd --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Primitem.cs @@ -0,0 +1,45 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Primitem +{ + public int? InvType { get; set; } + + public int? AssetType { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public long? CreationDate { get; set; } + + public int? NextPermissions { get; set; } + + public int? CurrentPermissions { get; set; } + + public int? BasePermissions { get; set; } + + public int? EveryonePermissions { get; set; } + + public int? GroupPermissions { get; set; } + + public int Flags { get; set; } + + public string ItemId { get; set; } + + public string PrimId { get; set; } + + public string AssetId { get; set; } + + public string ParentFolderId { get; set; } + + public string CreatorId { get; set; } + + public string OwnerId { get; set; } + + public string GroupId { get; set; } + + public string LastOwnerId { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Primshape.cs b/Source/OpenSim.Data.Model/Region/Primshape.cs new file mode 100644 index 00000000000..ada20788991 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Primshape.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Primshape +{ + public int? Shape { get; set; } + + public double ScaleX { get; set; } + + public double ScaleY { get; set; } + + public double ScaleZ { get; set; } + + public int? Pcode { get; set; } + + public int? PathBegin { get; set; } + + public int? PathEnd { get; set; } + + public int? PathScaleX { get; set; } + + public int? PathScaleY { get; set; } + + public int? PathShearX { get; set; } + + public int? PathShearY { get; set; } + + public int? PathSkew { get; set; } + + public int? PathCurve { get; set; } + + public int? PathRadiusOffset { get; set; } + + public int? PathRevolutions { get; set; } + + public int? PathTaperX { get; set; } + + public int? PathTaperY { get; set; } + + public int? PathTwist { get; set; } + + public int? PathTwistBegin { get; set; } + + public int? ProfileBegin { get; set; } + + public int? ProfileEnd { get; set; } + + public int? ProfileCurve { get; set; } + + public int? ProfileHollow { get; set; } + + public int? State { get; set; } + + public byte[] Texture { get; set; } + + public byte[] ExtraParams { get; set; } + + public string Uuid { get; set; } + + public string Media { get; set; } + + public int LastAttachPoint { get; set; } + + public byte[] MatOvrd { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Regionban.cs b/Source/OpenSim.Data.Model/Region/Regionban.cs new file mode 100644 index 00000000000..4eb3d38b290 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Regionban.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Regionban +{ + public string RegionUuid { get; set; } + + public string BannedUuid { get; set; } + + public string BannedIp { get; set; } + + public string BannedIpHostMask { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Regionenvironment.cs b/Source/OpenSim.Data.Model/Region/Regionenvironment.cs new file mode 100644 index 00000000000..89d3b2c511f --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Regionenvironment.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Regionenvironment +{ + public string RegionId { get; set; } + + public string LlsdSettings { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Regionextra.cs b/Source/OpenSim.Data.Model/Region/Regionextra.cs new file mode 100644 index 00000000000..faa557a3a51 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Regionextra.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Regionextra +{ + public string RegionId { get; set; } + + public string Name { get; set; } + + public string Value { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Regionsetting.cs b/Source/OpenSim.Data.Model/Region/Regionsetting.cs new file mode 100644 index 00000000000..10b56a9b06e --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Regionsetting.cs @@ -0,0 +1,107 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Regionsetting +{ + public string RegionUuid { get; set; } + + public int BlockTerraform { get; set; } + + public int BlockFly { get; set; } + + public int AllowDamage { get; set; } + + public int RestrictPushing { get; set; } + + public int AllowLandResell { get; set; } + + public int AllowLandJoinDivide { get; set; } + + public int BlockShowInSearch { get; set; } + + public int AgentLimit { get; set; } + + public double ObjectBonus { get; set; } + + public int Maturity { get; set; } + + public int DisableScripts { get; set; } + + public int DisableCollisions { get; set; } + + public int DisablePhysics { get; set; } + + public string TerrainTexture1 { get; set; } + + public string TerrainTexture2 { get; set; } + + public string TerrainTexture3 { get; set; } + + public string TerrainTexture4 { get; set; } + + public double Elevation1Nw { get; set; } + + public double Elevation2Nw { get; set; } + + public double Elevation1Ne { get; set; } + + public double Elevation2Ne { get; set; } + + public double Elevation1Se { get; set; } + + public double Elevation2Se { get; set; } + + public double Elevation1Sw { get; set; } + + public double Elevation2Sw { get; set; } + + public double WaterHeight { get; set; } + + public double TerrainRaiseLimit { get; set; } + + public double TerrainLowerLimit { get; set; } + + public int UseEstateSun { get; set; } + + public int FixedSun { get; set; } + + public double SunPosition { get; set; } + + public string Covenant { get; set; } + + public sbyte Sandbox { get; set; } + + public double Sunvectorx { get; set; } + + public double Sunvectory { get; set; } + + public double Sunvectorz { get; set; } + + public string LoadedCreationId { get; set; } + + public uint LoadedCreationDatetime { get; set; } + + public string MapTileId { get; set; } + + public string TelehubObject { get; set; } + + public string ParcelTileId { get; set; } + + public uint CovenantDatetime { get; set; } + + public sbyte BlockSearch { get; set; } + + public sbyte Casino { get; set; } + + public string CacheId { get; set; } + + public string TerrainPBR1 { get; set; } + + public string TerrainPBR2 { get; set; } + + public string TerrainPBR3 { get; set; } + + public string TerrainPBR4 { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Regionwindlight.cs b/Source/OpenSim.Data.Model/Region/Regionwindlight.cs new file mode 100644 index 00000000000..fa66229b6d7 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Regionwindlight.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Regionwindlight +{ + public string RegionId { get; set; } + + public float WaterColorR { get; set; } + + public float WaterColorG { get; set; } + + public float WaterColorB { get; set; } + + public float WaterFogDensityExponent { get; set; } + + public float UnderwaterFogModifier { get; set; } + + public float ReflectionWaveletScale1 { get; set; } + + public float ReflectionWaveletScale2 { get; set; } + + public float ReflectionWaveletScale3 { get; set; } + + public float FresnelScale { get; set; } + + public float FresnelOffset { get; set; } + + public float RefractScaleAbove { get; set; } + + public float RefractScaleBelow { get; set; } + + public float BlurMultiplier { get; set; } + + public float BigWaveDirectionX { get; set; } + + public float BigWaveDirectionY { get; set; } + + public float LittleWaveDirectionX { get; set; } + + public float LittleWaveDirectionY { get; set; } + + public string NormalMapTexture { get; set; } + + public float HorizonR { get; set; } + + public float HorizonG { get; set; } + + public float HorizonB { get; set; } + + public float HorizonI { get; set; } + + public float HazeHorizon { get; set; } + + public float BlueDensityR { get; set; } + + public float BlueDensityG { get; set; } + + public float BlueDensityB { get; set; } + + public float BlueDensityI { get; set; } + + public float HazeDensity { get; set; } + + public float DensityMultiplier { get; set; } + + public float DistanceMultiplier { get; set; } + + public uint MaxAltitude { get; set; } + + public float SunMoonColorR { get; set; } + + public float SunMoonColorG { get; set; } + + public float SunMoonColorB { get; set; } + + public float SunMoonColorI { get; set; } + + public float SunMoonPosition { get; set; } + + public float AmbientR { get; set; } + + public float AmbientG { get; set; } + + public float AmbientB { get; set; } + + public float AmbientI { get; set; } + + public float EastAngle { get; set; } + + public float SunGlowFocus { get; set; } + + public float SunGlowSize { get; set; } + + public float SceneGamma { get; set; } + + public float StarBrightness { get; set; } + + public float CloudColorR { get; set; } + + public float CloudColorG { get; set; } + + public float CloudColorB { get; set; } + + public float CloudColorI { get; set; } + + public float CloudX { get; set; } + + public float CloudY { get; set; } + + public float CloudDensity { get; set; } + + public float CloudCoverage { get; set; } + + public float CloudScale { get; set; } + + public float CloudDetailX { get; set; } + + public float CloudDetailY { get; set; } + + public float CloudDetailDensity { get; set; } + + public float CloudScrollX { get; set; } + + public byte CloudScrollXLock { get; set; } + + public float CloudScrollY { get; set; } + + public byte CloudScrollYLock { get; set; } + + public byte DrawClassicClouds { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/SpawnPoint.cs b/Source/OpenSim.Data.Model/Region/SpawnPoint.cs new file mode 100644 index 00000000000..0401b14988f --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/SpawnPoint.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class SpawnPoint +{ + public string RegionId { get; set; } + + public float Yaw { get; set; } + + public float Pitch { get; set; } + + public float Distance { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Region/Terrain.cs b/Source/OpenSim.Data.Model/Region/Terrain.cs new file mode 100644 index 00000000000..dc8c28a70a8 --- /dev/null +++ b/Source/OpenSim.Data.Model/Region/Terrain.cs @@ -0,0 +1,13 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Region; + +public partial class Terrain +{ + public string RegionUuid { get; set; } + + public int? Revision { get; set; } + + public byte[] Heightfield { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Allparcel.cs b/Source/OpenSim.Data.Model/Search/Allparcel.cs new file mode 100644 index 00000000000..6633b2a0c7b --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Allparcel.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Allparcel +{ + public string RegionUuid { get; set; } + + public string Parcelname { get; set; } + + public string OwnerUuid { get; set; } + + public string GroupUuid { get; set; } + + public string Landingpoint { get; set; } + + public string ParcelUuid { get; set; } + + public string InfoUuid { get; set; } + + public int Parcelarea { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Classified.cs b/Source/OpenSim.Data.Model/Search/Classified.cs new file mode 100644 index 00000000000..cf87636b290 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Classified.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Classified +{ + public string Classifieduuid { get; set; } + + public string Creatoruuid { get; set; } + + public int Creationdate { get; set; } + + public int Expirationdate { get; set; } + + public string Category { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Parceluuid { get; set; } + + public int Parentestate { get; set; } + + public string Snapshotuuid { get; set; } + + public string Simname { get; set; } + + public string Posglobal { get; set; } + + public string Parcelname { get; set; } + + public int Classifiedflags { get; set; } + + public int Priceforlisting { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Event.cs b/Source/OpenSim.Data.Model/Search/Event.cs new file mode 100644 index 00000000000..8b605d92c77 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Event.cs @@ -0,0 +1,37 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Event +{ + public string Owneruuid { get; set; } + + public string Name { get; set; } + + public uint Eventid { get; set; } + + public string Creatoruuid { get; set; } + + public int Category { get; set; } + + public string Description { get; set; } + + public int DateUtc { get; set; } + + public int Duration { get; set; } + + public bool Covercharge { get; set; } + + public int Coveramount { get; set; } + + public string Simname { get; set; } + + public string ParcelUuid { get; set; } + + public string GlobalPos { get; set; } + + public int Eventflags { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Hostsregister.cs b/Source/OpenSim.Data.Model/Search/Hostsregister.cs new file mode 100644 index 00000000000..e9719cdaf6e --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Hostsregister.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Hostsregister +{ + public string Host { get; set; } + + public int Port { get; set; } + + public int Register { get; set; } + + public int Nextcheck { get; set; } + + public bool Checked { get; set; } + + public int Failcounter { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/OpenSimSearchContext.cs b/Source/OpenSim.Data.Model/Search/OpenSimSearchContext.cs new file mode 100644 index 00000000000..5ba115e5649 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/OpenSimSearchContext.cs @@ -0,0 +1,464 @@ +using System; +using System.Collections.Generic; +using Microsoft.EntityFrameworkCore; + +namespace OpenSim.Data.Model.Search; + +public partial class OpenSimSearchContext : DbContext +{ + public OpenSimSearchContext(DbContextOptions options) + : base(options) + { + } + + public virtual DbSet Allparcels { get; set; } + + public virtual DbSet Classifieds { get; set; } + + public virtual DbSet Events { get; set; } + + public virtual DbSet Hostsregisters { get; set; } + + public virtual DbSet Objects { get; set; } + + public virtual DbSet Parcels { get; set; } + + public virtual DbSet Parcelsales { get; set; } + + public virtual DbSet Popularplaces { get; set; } + + public virtual DbSet Regions { get; set; } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + modelBuilder + .UseCollation("utf8mb4_0900_ai_ci") + .HasCharSet("utf8mb4"); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ParcelUuid).HasName("PRIMARY"); + + entity + .ToTable("allparcels") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.HasIndex(e => e.RegionUuid, "regionUUID"); + + entity.Property(e => e.ParcelUuid) + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("parcelUUID"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.GroupUuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("groupUUID"); + entity.Property(e => e.InfoUuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("infoUUID"); + entity.Property(e => e.Landingpoint) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("landingpoint"); + entity.Property(e => e.OwnerUuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("ownerUUID"); + entity.Property(e => e.Parcelarea).HasColumnName("parcelarea"); + entity.Property(e => e.Parcelname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("parcelname"); + entity.Property(e => e.RegionUuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("regionUUID"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Classifieduuid).HasName("PRIMARY"); + + entity + .ToTable("classifieds") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.Property(e => e.Classifieduuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("classifieduuid"); + entity.Property(e => e.Category) + .IsRequired() + .HasMaxLength(20) + .HasColumnName("category"); + entity.Property(e => e.Classifiedflags).HasColumnName("classifiedflags"); + entity.Property(e => e.Creationdate).HasColumnName("creationdate"); + entity.Property(e => e.Creatoruuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("creatoruuid"); + entity.Property(e => e.Description) + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + entity.Property(e => e.Expirationdate).HasColumnName("expirationdate"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("name"); + entity.Property(e => e.Parcelname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("parcelname"); + entity.Property(e => e.Parceluuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parceluuid"); + entity.Property(e => e.Parentestate).HasColumnName("parentestate"); + entity.Property(e => e.Posglobal) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("posglobal"); + entity.Property(e => e.Priceforlisting).HasColumnName("priceforlisting"); + entity.Property(e => e.Simname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("simname"); + entity.Property(e => e.Snapshotuuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("snapshotuuid"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Eventid).HasName("PRIMARY"); + + entity + .ToTable("events") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_general_ci"); + + entity.Property(e => e.Eventid).HasColumnName("eventid"); + entity.Property(e => e.Category).HasColumnName("category"); + entity.Property(e => e.Coveramount).HasColumnName("coveramount"); + entity.Property(e => e.Covercharge).HasColumnName("covercharge"); + entity.Property(e => e.Creatoruuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("creatoruuid"); + entity.Property(e => e.DateUtc).HasColumnName("dateUTC"); + entity.Property(e => e.Description) + .IsRequired() + .HasColumnType("text") + .HasColumnName("description"); + entity.Property(e => e.Duration).HasColumnName("duration"); + entity.Property(e => e.Eventflags).HasColumnName("eventflags"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.GlobalPos) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("globalPos"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("name"); + entity.Property(e => e.Owneruuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("owneruuid"); + entity.Property(e => e.ParcelUuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parcelUUID"); + entity.Property(e => e.Simname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("simname"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.Host, e.Port }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("hostsregister") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.Property(e => e.Host).HasColumnName("host"); + entity.Property(e => e.Port).HasColumnName("port"); + entity.Property(e => e.Checked).HasColumnName("checked"); + entity.Property(e => e.Failcounter).HasColumnName("failcounter"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.Nextcheck).HasColumnName("nextcheck"); + entity.Property(e => e.Register).HasColumnName("register"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.Objectuuid, e.Parceluuid }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("objects") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.Property(e => e.Objectuuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("objectuuid"); + entity.Property(e => e.Parceluuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parceluuid"); + entity.Property(e => e.Description) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("description"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.Location) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("location"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("name"); + entity.Property(e => e.Regionuuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .IsFixedLength() + .HasColumnName("regionuuid"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.RegionUuid, e.ParcelUuid }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("parcels") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.HasIndex(e => e.Description, "description"); + + entity.HasIndex(e => e.Dwell, "dwell"); + + entity.HasIndex(e => e.Parcelname, "name"); + + entity.HasIndex(e => e.Searchcategory, "searchcategory"); + + entity.Property(e => e.RegionUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("regionUUID"); + entity.Property(e => e.ParcelUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parcelUUID"); + entity.Property(e => e.Build) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("build"); + entity.Property(e => e.Description) + .IsRequired() + .HasColumnName("description"); + entity.Property(e => e.Dwell).HasColumnName("dwell"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.ImageUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("imageUUID"); + entity.Property(e => e.Infouuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("''") + .HasColumnName("infouuid"); + entity.Property(e => e.Landingpoint) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("landingpoint"); + entity.Property(e => e.Mature) + .IsRequired() + .HasMaxLength(10) + .HasDefaultValueSql("'PG'") + .HasColumnName("mature"); + entity.Property(e => e.Parcelname) + .IsRequired() + .HasColumnName("parcelname"); + entity.Property(e => e.Public) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("public"); + entity.Property(e => e.Script) + .IsRequired() + .HasColumnType("enum('true','false')") + .HasColumnName("script"); + entity.Property(e => e.Searchcategory) + .IsRequired() + .HasMaxLength(50) + .HasColumnName("searchcategory"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => new { e.RegionUuid, e.ParcelUuid }) + .HasName("PRIMARY") + .HasAnnotation("MySql:IndexPrefixLength", new[] { 0, 0 }); + + entity + .ToTable("parcelsales") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.Property(e => e.RegionUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("regionUUID"); + entity.Property(e => e.ParcelUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parcelUUID"); + entity.Property(e => e.Area).HasColumnName("area"); + entity.Property(e => e.Dwell).HasColumnName("dwell"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.InfoUuid) + .IsRequired() + .HasMaxLength(36) + .HasDefaultValueSql("'00000000-0000-0000-0000-000000000000'") + .IsFixedLength() + .HasColumnName("infoUUID"); + entity.Property(e => e.Landingpoint) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("landingpoint"); + entity.Property(e => e.Mature) + .IsRequired() + .HasMaxLength(10) + .HasDefaultValueSql("'PG'") + .HasColumnName("mature"); + entity.Property(e => e.Parcelname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("parcelname"); + entity.Property(e => e.Parentestate) + .HasDefaultValueSql("'1'") + .HasColumnName("parentestate"); + entity.Property(e => e.Saleprice).HasColumnName("saleprice"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.ParcelUuid).HasName("PRIMARY"); + + entity + .ToTable("popularplaces") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.Property(e => e.ParcelUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("parcelUUID"); + entity.Property(e => e.Dwell).HasColumnName("dwell"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.HasPicture).HasColumnName("has_picture"); + entity.Property(e => e.InfoUuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("infoUUID"); + entity.Property(e => e.Mature) + .IsRequired() + .HasMaxLength(10) + .HasColumnName("mature"); + entity.Property(e => e.Name) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("name"); + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.RegionUuid).HasName("PRIMARY"); + + entity + .ToTable("regions") + .HasCharSet("utf8mb3") + .UseCollation("utf8mb3_unicode_ci"); + + entity.Property(e => e.RegionUuid) + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("regionUUID"); + entity.Property(e => e.GatekeeperUrl) + .HasMaxLength(255) + .HasColumnName("gatekeeperURL"); + entity.Property(e => e.Owner) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("owner"); + entity.Property(e => e.Owneruuid) + .IsRequired() + .HasMaxLength(36) + .IsFixedLength() + .HasColumnName("owneruuid"); + entity.Property(e => e.Regionhandle) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("regionhandle"); + entity.Property(e => e.Regionname) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("regionname"); + entity.Property(e => e.Url) + .IsRequired() + .HasMaxLength(255) + .HasColumnName("url"); + }); + + OnModelCreatingPartial(modelBuilder); + } + + partial void OnModelCreatingPartial(ModelBuilder modelBuilder); +} diff --git a/Source/OpenSim.Data.Model/Search/Parcel.cs b/Source/OpenSim.Data.Model/Search/Parcel.cs new file mode 100644 index 00000000000..9f1ac7548e1 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Parcel.cs @@ -0,0 +1,35 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Parcel +{ + public string ParcelUuid { get; set; } + + public string RegionUuid { get; set; } + + public string Parcelname { get; set; } + + public string Landingpoint { get; set; } + + public string Description { get; set; } + + public string Searchcategory { get; set; } + + public string Build { get; set; } + + public string Script { get; set; } + + public string Public { get; set; } + + public float Dwell { get; set; } + + public string Infouuid { get; set; } + + public string Mature { get; set; } + + public string GatekeeperUrl { get; set; } + + public string ImageUuid { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Parcelsale.cs b/Source/OpenSim.Data.Model/Search/Parcelsale.cs new file mode 100644 index 00000000000..47b68137c36 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Parcelsale.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Parcelsale +{ + public string RegionUuid { get; set; } + + public string Parcelname { get; set; } + + public string ParcelUuid { get; set; } + + public int Area { get; set; } + + public int Saleprice { get; set; } + + public string Landingpoint { get; set; } + + public string InfoUuid { get; set; } + + public int Dwell { get; set; } + + public int Parentestate { get; set; } + + public string Mature { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Popularplace.cs b/Source/OpenSim.Data.Model/Search/Popularplace.cs new file mode 100644 index 00000000000..7b8b4b6a6c6 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Popularplace.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Popularplace +{ + public string ParcelUuid { get; set; } + + public string Name { get; set; } + + public float Dwell { get; set; } + + public string InfoUuid { get; set; } + + public bool HasPicture { get; set; } + + public string Mature { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/Region.cs b/Source/OpenSim.Data.Model/Search/Region.cs new file mode 100644 index 00000000000..15b500197d6 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/Region.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class Region +{ + public string Regionname { get; set; } + + public string RegionUuid { get; set; } + + public string Regionhandle { get; set; } + + public string Url { get; set; } + + public string Owner { get; set; } + + public string Owneruuid { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/Search/SearchObject.cs b/Source/OpenSim.Data.Model/Search/SearchObject.cs new file mode 100644 index 00000000000..aa400678cc4 --- /dev/null +++ b/Source/OpenSim.Data.Model/Search/SearchObject.cs @@ -0,0 +1,21 @@ +using System; +using System.Collections.Generic; + +namespace OpenSim.Data.Model.Search; + +public partial class SearchObject +{ + public string Objectuuid { get; set; } + + public string Parceluuid { get; set; } + + public string Location { get; set; } + + public string Name { get; set; } + + public string Description { get; set; } + + public string Regionuuid { get; set; } + + public string GatekeeperUrl { get; set; } +} diff --git a/Source/OpenSim.Data.Model/issues.txt b/Source/OpenSim.Data.Model/issues.txt new file mode 100644 index 00000000000..b5bf9621f4e --- /dev/null +++ b/Source/OpenSim.Data.Model/issues.txt @@ -0,0 +1,2 @@ +The column 'AgentPrefs.LanguageIsPublic' would normally be mapped to a non-nullable bool property, but it has a default constraint. Such a column is mapped to a nullable bool property to allow a difference between setting the property to false and invoking the default constraint. See https://go.microsoft.com/fwlink/?linkid=851278 for details. + diff --git a/Source/OpenSim.Services.AISv3/Controllers/WeatherForecastController.cs b/Source/OpenSim.Services.AISv3/Controllers/WeatherForecastController.cs new file mode 100644 index 00000000000..6c8685bb31f --- /dev/null +++ b/Source/OpenSim.Services.AISv3/Controllers/WeatherForecastController.cs @@ -0,0 +1,32 @@ +using Microsoft.AspNetCore.Mvc; + +namespace OpenSim.Services.AISv3.Controllers; + +[ApiController] +[Route("[controller]")] +public class WeatherForecastController : ControllerBase +{ + private static readonly string[] Summaries = new[] + { + "Freezing", "Bracing", "Chilly", "Cool", "Mild", "Warm", "Balmy", "Hot", "Sweltering", "Scorching" + }; + + private readonly ILogger _logger; + + public WeatherForecastController(ILogger logger) + { + _logger = logger; + } + + [HttpGet(Name = "GetWeatherForecast")] + public IEnumerable Get() + { + return Enumerable.Range(1, 5).Select(index => new WeatherForecast + { + Date = DateOnly.FromDateTime(DateTime.Now.AddDays(index)), + TemperatureC = Random.Shared.Next(-20, 55), + Summary = Summaries[Random.Shared.Next(Summaries.Length)] + }) + .ToArray(); + } +} diff --git a/Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.csproj b/Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.csproj new file mode 100644 index 00000000000..042e3502ba0 --- /dev/null +++ b/Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.csproj @@ -0,0 +1,14 @@ + + + + net8.0 + enable + enable + + + + + + + + diff --git a/Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.http b/Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.http new file mode 100644 index 00000000000..92ffc34a0e4 --- /dev/null +++ b/Source/OpenSim.Services.AISv3/OpenSim.Services.AISv3.http @@ -0,0 +1,6 @@ +@OpenSim.Services.AISv3_HostAddress = http://localhost:5126 + +GET {{OpenSim.Services.AISv3_HostAddress}}/weatherforecast/ +Accept: application/json + +### diff --git a/Source/OpenSim.Services.AISv3/Program.cs b/Source/OpenSim.Services.AISv3/Program.cs new file mode 100644 index 00000000000..48863a6d6cd --- /dev/null +++ b/Source/OpenSim.Services.AISv3/Program.cs @@ -0,0 +1,25 @@ +var builder = WebApplication.CreateBuilder(args); + +// Add services to the container. + +builder.Services.AddControllers(); +// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle +builder.Services.AddEndpointsApiExplorer(); +builder.Services.AddSwaggerGen(); + +var app = builder.Build(); + +// Configure the HTTP request pipeline. +if (app.Environment.IsDevelopment()) +{ + app.UseSwagger(); + app.UseSwaggerUI(); +} + +app.UseHttpsRedirection(); + +app.UseAuthorization(); + +app.MapControllers(); + +app.Run(); diff --git a/Source/OpenSim.Services.AISv3/WeatherForecast.cs b/Source/OpenSim.Services.AISv3/WeatherForecast.cs new file mode 100644 index 00000000000..9ab4ba00628 --- /dev/null +++ b/Source/OpenSim.Services.AISv3/WeatherForecast.cs @@ -0,0 +1,12 @@ +namespace OpenSim.Services.AISv3; + +public class WeatherForecast +{ + public DateOnly Date { get; set; } + + public int TemperatureC { get; set; } + + public int TemperatureF => 32 + (int)(TemperatureC / 0.5556); + + public string? Summary { get; set; } +} diff --git a/Source/OpenSim.Services.AISv3/appsettings.Development.json b/Source/OpenSim.Services.AISv3/appsettings.Development.json new file mode 100644 index 00000000000..0c208ae9181 --- /dev/null +++ b/Source/OpenSim.Services.AISv3/appsettings.Development.json @@ -0,0 +1,8 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + } +} diff --git a/Source/OpenSim.Services.AISv3/appsettings.json b/Source/OpenSim.Services.AISv3/appsettings.json new file mode 100644 index 00000000000..10f68b8c8b4 --- /dev/null +++ b/Source/OpenSim.Services.AISv3/appsettings.json @@ -0,0 +1,9 @@ +{ + "Logging": { + "LogLevel": { + "Default": "Information", + "Microsoft.AspNetCore": "Warning" + } + }, + "AllowedHosts": "*" +} diff --git a/Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventory2HandlerTests.cs b/Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventory2HandlerTests.cs new file mode 100644 index 00000000000..be171a134fd --- /dev/null +++ b/Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventory2HandlerTests.cs @@ -0,0 +1,160 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Capabilities.Handlers.FetchInventory.Tests +{ + [TestFixture] + public class FetchInventory2HandlerTests : OpenSimTestCase + { + private UUID m_userID = UUID.Random(); + private Scene m_scene; + private UUID m_rootFolderID; + private UUID m_notecardsFolder; + private UUID m_objectsFolder; + + private void Init() + { + // Create an inventory that looks like this: + // + // /My Inventory + // + // /Objects + // Object 1 + // Object 2 + // Object 3 + // /Notecards + // Notecard 1 + // Notecard 2 + // Notecard 3 + // Notecard 4 + // Notecard 5 + + m_scene = new SceneHelpers().SetupScene(); + + m_scene.InventoryService.CreateUserInventory(m_userID); + + m_rootFolderID = m_scene.InventoryService.GetRootFolder(m_userID).ID; + + InventoryFolderBase of = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object); + m_objectsFolder = of.ID; + + // Add 3 objects + InventoryItemBase item; + for (int i = 1; i <= 3; i++) + { + item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-0000000000b" + i), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Object; + item.Folder = m_objectsFolder; + item.Name = "Object " + i; + m_scene.InventoryService.AddItem(item); + } + + InventoryFolderBase ncf = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Notecard); + m_notecardsFolder = ncf.ID; + + // Add 5 notecards + for (int i = 1; i <= 5; i++) + { + item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-00000000000" + i), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Notecard; + item.Folder = m_notecardsFolder; + item.Name = "Notecard " + i; + m_scene.InventoryService.AddItem(item); + } + + } + + [Test] + public void Test_001_RequestOne() + { + TestHelpers.InMethod(); + + Init(); + + FetchInventory2Handler handler = new FetchInventory2Handler(m_scene.InventoryService, m_userID); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "itemsitem_id"; + request += "10000000-0000-0000-0000-000000000001"; // Notecard 1 + request += ""; + + string llsdresponse = handler.FetchInventoryRequest(request, "/FETCH", string.Empty, req, resp); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID"); + + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Response does not contain item uuid"); + Assert.That(llsdresponse.Contains("Notecard 1"), Is.True, "Response does not contain item Name"); + Console.WriteLine(llsdresponse); + } + + [Test] + public void Test_002_RequestMany() + { + TestHelpers.InMethod(); + + Init(); + + FetchInventory2Handler handler = new FetchInventory2Handler(m_scene.InventoryService, m_userID); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + + string request = "items"; + request += "item_id10000000-0000-0000-0000-000000000001"; // Notecard 1 + request += "item_id10000000-0000-0000-0000-000000000002"; // Notecard 2 + request += "item_id10000000-0000-0000-0000-000000000003"; // Notecard 3 + request += "item_id10000000-0000-0000-0000-000000000004"; // Notecard 4 + request += "item_id10000000-0000-0000-0000-000000000005"; // Notecard 5 + request += ""; + + string llsdresponse = handler.FetchInventoryRequest(request, "/FETCH", string.Empty, req, resp); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID"); + + Console.WriteLine(llsdresponse); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Response does not contain notecard 1"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000002"), Is.True, "Response does not contain notecard 2"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000003"), Is.True, "Response does not contain notecard 3"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000004"), Is.True, "Response does not contain notecard 4"); + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000005"), Is.True, "Response does not contain notecard 5"); + } + + } + +} \ No newline at end of file diff --git a/Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs b/Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs new file mode 100644 index 00000000000..f581ca2bf48 --- /dev/null +++ b/Tests/OpenSim.Capabilities.Handlers.Tests/FetchInventory/Tests/FetchInventoryDescendents2HandlerTests.cs @@ -0,0 +1,295 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Text.RegularExpressions; + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Capabilities.Handlers.FetchInventory.Tests +{ + [TestFixture] + public class FetchInventoryDescendents2HandlerTests : OpenSimTestCase + { + private UUID m_userID = new UUID("00000000-0000-0000-0000-000000000001"); + private Scene m_scene; + private UUID m_rootFolderID; + private int m_rootDescendents; + private UUID m_notecardsFolder; + private UUID m_objectsFolder; + + private void Init() + { + // Create an inventory that looks like this: + // + // /My Inventory + // + // /Objects + // Some Object + // /Notecards + // Notecard 1 + // Notecard 2 + // /Test Folder + // Link to notecard -> /Notecards/Notecard 2 + // Link to Objects folder -> /Objects + + m_scene = new SceneHelpers().SetupScene(); + + m_scene.InventoryService.CreateUserInventory(m_userID); + + m_rootFolderID = m_scene.InventoryService.GetRootFolder(m_userID).ID; + + InventoryFolderBase of = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object); + m_objectsFolder = of.ID; + + // Add an object + InventoryItemBase item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-00000000000b"), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Object; + item.Folder = m_objectsFolder; + item.Name = "Some Object"; + m_scene.InventoryService.AddItem(item); + + InventoryFolderBase ncf = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Notecard); + m_notecardsFolder = ncf.ID; + + // Add a notecard + item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-000000000001"), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Notecard; + item.Folder = m_notecardsFolder; + item.Name = "Test Notecard 1"; + m_scene.InventoryService.AddItem(item); + // Add another notecard + item.ID = new UUID("20000000-0000-0000-0000-000000000002"); + item.AssetID = new UUID("a0000000-0000-0000-0000-00000000000a"); + item.Name = "Test Notecard 2"; + m_scene.InventoryService.AddItem(item); + + // Add a folder + InventoryFolderBase folder = new InventoryFolderBase(new UUID("f0000000-0000-0000-0000-00000000000f"), "Test Folder", m_userID, m_rootFolderID); + folder.Type = (short)FolderType.None; + m_scene.InventoryService.AddFolder(folder); + + // Add a link to notecard 2 in Test Folder + item.AssetID = item.ID; // use item ID of notecard 2 + item.ID = new UUID("40000000-0000-0000-0000-000000000004"); + item.AssetType = (int)AssetType.Link; + item.Folder = folder.ID; + item.Name = "Link to notecard"; + m_scene.InventoryService.AddItem(item); + + // Add a link to the Objects folder in Test Folder + item.AssetID = m_scene.InventoryService.GetFolderForType(m_userID, FolderType.Object).ID; // use item ID of Objects folder + item.ID = new UUID("50000000-0000-0000-0000-000000000005"); + item.AssetType = (int)AssetType.LinkFolder; + item.Folder = folder.ID; + item.Name = "Link to Objects folder"; + m_scene.InventoryService.AddItem(item); + + InventoryCollection coll = m_scene.InventoryService.GetFolderContent(m_userID, m_rootFolderID); + m_rootDescendents = coll.Items.Count + coll.Folders.Count; + Console.WriteLine("Number of descendents: " + m_rootDescendents); + } + + private string dorequest(FetchInvDescHandler handler, string request) + { + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + using(ExpiringKey bad = new ExpiringKey(5000)) // bad but this is test + using (MemoryStream ms = new MemoryStream(Utils.StringToBytes(request), false)) + { + req.InputStream = ms; + handler.FetchInventoryDescendentsRequest(req, resp, bad); + } + return Util.UTF8.GetString(resp.RawBuffer); + } + + [Test] + public void Test_001_SimpleFolder() + { + TestHelpers.InMethod(); + + Init(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + + string request = "foldersfetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id"; + request += m_userID.ToString(); + request += "sort_order1"; + + string llsdresponse = dorequest(handler, request); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + Assert.That(llsdresponse.Contains(m_userID.ToString()), Is.True, "Response should contain userID"); + + string descendents = "descendents" + m_rootDescendents + ""; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents"); + Console.WriteLine(llsdresponse); + } + + [Test] + public void Test_002_MultipleFolders() + { + TestHelpers.InMethod(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + + string request = "folders"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000001sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_notecardsFolder; + request += "owner_id00000000-0000-0000-0000-000000000001sort_order1"; + request += ""; + + string llsdresponse = dorequest(handler, request); + Console.WriteLine(llsdresponse); + + string descendents = "descendents" + m_rootDescendents + ""; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for root folder"); + descendents = "descendents2"; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for Notecard folder"); + + Assert.That(llsdresponse.Contains("10000000-0000-0000-0000-000000000001"), Is.True, "Notecard 1 is missing from response"); + Assert.That(llsdresponse.Contains("20000000-0000-0000-0000-000000000002"), Is.True, "Notecard 2 is missing from response"); + } + + [Test] + public void Test_003_Links() + { + TestHelpers.InMethod(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + + string request = "foldersfetch_folders1fetch_items1folder_id"; + request += "f0000000-0000-0000-0000-00000000000f"; + request += "owner_id00000000-0000-0000-0000-000000000001sort_order1"; + + string llsdresponse = dorequest(handler, request); + Console.WriteLine(llsdresponse); + + string descendents = "descendents2"; + Assert.That(llsdresponse.Contains(descendents), Is.True, "Incorrect number of descendents for Test Folder"); + + // Make sure that the note card link is included + Assert.That(llsdresponse.Contains("Link to notecard"), Is.True, "Link to notecard is missing"); + + //Make sure the notecard item itself is included + Assert.That(llsdresponse.Contains("Test Notecard 2"), Is.True, "Notecard 2 item (the source) is missing"); + + // Make sure that the source item is before the link item + int pos1 = llsdresponse.IndexOf("Test Notecard 2"); + int pos2 = llsdresponse.IndexOf("Link to notecard"); + Assert.Less(pos1, pos2, "Source of link is after link"); + + // Make sure the folder link is included + Assert.That(llsdresponse.Contains("Link to Objects folder"), Is.True, "Link to Objects folder is missing"); + +/* contents of link folder are not supposed to be listed + // Make sure the objects inside the Objects folder are included + // Note: I'm not entirely sure this is needed, but that's what I found in the implementation + Assert.That(llsdresponse.Contains("Some Object"), Is.True, "Some Object item (contents of the source) is missing"); +*/ + // Make sure that the source item is before the link item + pos1 = llsdresponse.IndexOf("Some Object"); + pos2 = llsdresponse.IndexOf("Link to Objects folder"); + Assert.Less(pos1, pos2, "Contents of source of folder link is after folder link"); + } + + [Test] + public void Test_004_DuplicateFolders() + { + TestHelpers.InMethod(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + + string request = "folders"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_notecardsFolder; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_rootFolderID; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += "fetch_folders1fetch_items1folder_id"; + request += m_notecardsFolder; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + request += ""; + + string llsdresponse = dorequest(handler, request); + Console.WriteLine(llsdresponse); + + string root_folder = "folder_id" + m_rootFolderID + ""; + string notecards_folder = "folder_id" + m_notecardsFolder + ""; + string notecards_category = "category_id" + m_notecardsFolder + ""; + + Assert.That(llsdresponse.Contains(root_folder), "Missing root folder"); + Assert.That(llsdresponse.Contains(notecards_folder), "Missing notecards folder"); + int count = Regex.Matches(llsdresponse, root_folder).Count; + Assert.AreEqual(1, count, "More than 1 root folder in response"); + count = Regex.Matches(llsdresponse, notecards_folder).Count; + Assert.AreEqual(1, count, "More than 1 notecards folder in response"); + count = Regex.Matches(llsdresponse, notecards_category).Count; + Assert.AreEqual(1, count, "More than 1 notecards folder in response"); // Notecards will also be a category on root + } + + [Test] + public void Test_005_FolderZero() + { + + TestHelpers.InMethod(); + + Init(); + + FetchInvDescHandler handler = new FetchInvDescHandler(m_scene.InventoryService, null, m_scene); + + string request = "foldersfetch_folders1fetch_items1folder_id"; + request += UUID.Zero; + request += "owner_id00000000-0000-0000-0000-000000000000sort_order1"; + + string llsdresponse = dorequest(handler, request); + + Assert.That(llsdresponse != null, Is.True, "Incorrect null response"); + Assert.That(llsdresponse != string.Empty, Is.True, "Incorrect empty response"); + // we do return a answer now + //Assert.That(llsdresponse.Contains("bad_folders00000000-0000-0000-0000-000000000000"), Is.True, "Folder Zero should be a bad folder"); + + Console.WriteLine(llsdresponse); + } + } + +} \ No newline at end of file diff --git a/Tests/OpenSim.Capabilities.Handlers.Tests/GetTexture/Tests/GetTextureHandlerTests.cs b/Tests/OpenSim.Capabilities.Handlers.Tests/GetTexture/Tests/GetTextureHandlerTests.cs new file mode 100644 index 00000000000..d4e3fd9da84 --- /dev/null +++ b/Tests/OpenSim.Capabilities.Handlers.Tests/GetTexture/Tests/GetTextureHandlerTests.cs @@ -0,0 +1,65 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; + +using OpenMetaverse; + +using OpenSim.Capabilities.Handlers; +using OpenSim.Framework; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +using NUnit.Framework; + +/* +namespace OpenSim.Capabilities.Handlers.GetTexture.Tests +{ + [TestFixture] + public class GetTextureHandlerTests : OpenSimTestCase + { + [Test] + public void TestTextureNotFound() + { + TestHelpers.InMethod(); + + // Overkill - we only really need the asset service, not a whole scene. + Scene scene = new SceneHelpers().SetupScene(); + + GetTextureHandler handler = new GetTextureHandler("/gettexture", scene.AssetService, "TestGetTexture", null, null); + TestOSHttpRequest req = new TestOSHttpRequest(); + TestOSHttpResponse resp = new TestOSHttpResponse(); + req.Url = new Uri("http://localhost/?texture_id=00000000-0000-1111-9999-000000000012"); + handler.Handle(null, null, req, resp); + Assert.That(resp.StatusCode, Is.EqualTo((int)System.Net.HttpStatusCode.NotFound)); + } + } +} +*/ diff --git a/Tests/OpenSim.Capabilities.Handlers.Tests/OpenSim.Capabilities.Handlers.Tests.csproj b/Tests/OpenSim.Capabilities.Handlers.Tests/OpenSim.Capabilities.Handlers.Tests.csproj new file mode 100644 index 00000000000..5e22e7f9b7a --- /dev/null +++ b/Tests/OpenSim.Capabilities.Handlers.Tests/OpenSim.Capabilities.Handlers.Tests.csproj @@ -0,0 +1,54 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Clients.Assets.Tests/AssetsClient.cs b/Tests/OpenSim.Clients.Assets.Tests/AssetsClient.cs new file mode 100644 index 00000000000..af2beff7a21 --- /dev/null +++ b/Tests/OpenSim.Clients.Assets.Tests/AssetsClient.cs @@ -0,0 +1,110 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using Microsoft.Extensions.Logging; + +using OpenMetaverse; + +using OpenSim.Framework; + +namespace OpenSim.Tests.Clients.AssetsClient +{ + public class AssetsClient + { + private static ILogger m_logger; + + private static int m_MaxThreadID = 0; + private static readonly int NREQS = 150; + private static int m_NReceived = 0; + + public static void Main(string[] args) + { + m_logger ??= LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + // TODO: how to format output: consoleAppender.Layout = new PatternLayout("[%thread] - %message%newline"); + + string serverURI = "http://127.0.0.1:8003"; + if (args.Length > 1) + serverURI = args[1]; + int max1, max2; + ThreadPool.GetMaxThreads(out max1, out max2); + m_logger.LogInformation("[ASSET CLIENT]: Connecting to {0} max threads = {1} - {2}", serverURI, max1, max2); + ThreadPool.GetMinThreads(out max1, out max2); + m_logger.LogInformation("[ASSET CLIENT]: Connecting to {0} min threads = {1} - {2}", serverURI, max1, max2); + + if (!ThreadPool.SetMinThreads(1, 1)) + m_logger.LogWarning("[ASSET CLIENT]: Failed to set min threads"); + + if (!ThreadPool.SetMaxThreads(10, 3)) + m_logger.LogWarning("[ASSET CLIENT]: Failed to set max threads"); + + ThreadPool.GetMaxThreads(out max1, out max2); + m_logger.LogInformation("[ASSET CLIENT]: Connected to {0}. Post set max threads = {1} - {2}", serverURI, max1, max2); + ThreadPool.GetMinThreads(out max1, out max2); + m_logger.LogInformation("[ASSET CLIENT]: Connected to {0}. Post set min threads = {1} - {2}", serverURI, max1, max2); + + AssetServicesConnector m_Connector = new AssetServicesConnector(serverURI); + m_Connector.MaxAssetRequestConcurrency = 30; + + for (int i = 0; i < NREQS; i++) + { + UUID uuid = UUID.Random(); + m_Connector.Get(uuid.ToString(), null, ResponseReceived); + m_logger.LogInformation("[ASSET CLIENT]: [{0}] requested asset {1}", i, uuid); + } + + for (int i = 0; i < 500; i++) + { + var x = i; + ThreadPool.QueueUserWorkItem(delegate + { + Dummy(x); + }); + } + + Thread.Sleep(30 * 1000); + m_logger.LogInformation("[ASSET CLIENT]: Received responses {0}", m_NReceived); + } + + private static void ResponseReceived(string id, Object sender, AssetBase asset) + { + if (Thread.CurrentThread.ManagedThreadId > m_MaxThreadID) + m_MaxThreadID = Thread.CurrentThread.ManagedThreadId; + int max1, max2; + ThreadPool.GetAvailableThreads(out max1, out max2); + m_logger.LogInformation("[ASSET CLIENT]: Received asset {0} ({1}) ({2}-{3}) {4}", id, m_MaxThreadID, max1, max2, DateTime.Now.ToString("hh:mm:ss")); + m_NReceived++; + } + + private static void Dummy(int i) + { + int max1, max2; + ThreadPool.GetAvailableThreads(out max1, out max2); + m_logger.LogInformation("[ASSET CLIENT]: ({0}) Hello! {1} - {2} {3}", i, max1, max2, DateTime.Now.ToString("hh:mm:ss")); + Thread.Sleep(2000); + } + } +} diff --git a/Tests/OpenSim.Clients.Assets.Tests/OpenSim.Tests.Clients.AssetClient.csproj b/Tests/OpenSim.Clients.Assets.Tests/OpenSim.Tests.Clients.AssetClient.csproj new file mode 100644 index 00000000000..5a775254466 --- /dev/null +++ b/Tests/OpenSim.Clients.Assets.Tests/OpenSim.Tests.Clients.AssetClient.csproj @@ -0,0 +1,43 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Data.Tests/AssetTests.cs b/Tests/OpenSim.Data.Tests/AssetTests.cs new file mode 100644 index 00000000000..cf1ef6ecffb --- /dev/null +++ b/Tests/OpenSim.Data.Tests/AssetTests.cs @@ -0,0 +1,220 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common; +using System.Data.Common; + +// DBMS-specific: +using OpenSim.Data.MySQL; + +using System.Data.SQLite; +using OpenSim.Data.SQLite; +using MySqlConnector; + +namespace OpenSim.Data.Tests +{ + [TestFixture(Description = "Asset store tests (SQLite)")] + public class SQLiteAssetTests : AssetTests + { + } + + [TestFixture(Description = "Asset store tests (MySQL)")] + public class MySqlAssetTests : AssetTests + { + } + + public class AssetTests : BasicDataServiceTest + where TConn : DbConnection, new() + where TAssetData : AssetDataBase, new() + { + TAssetData m_db; + + public UUID uuid1 = UUID.Random(); + public UUID uuid2 = UUID.Random(); + public UUID uuid3 = UUID.Random(); + + public string critter1 = UUID.Random().ToString(); + public string critter2 = UUID.Random().ToString(); + public string critter3 = UUID.Random().ToString(); + + public byte[] data1 = new byte[100]; + + PropertyScrambler scrambler = new PropertyScrambler() + .DontScramble(x => x.ID) + .DontScramble(x => x.Type) + .DontScramble(x => x.FullID) + .DontScramble(x => x.Metadata.ID) + .DontScramble(x => x.Metadata.CreatorID) + .DontScramble(x => x.Metadata.ContentType) + .DontScramble(x => x.Metadata.FullID) + .DontScramble(x => x.Data); + + protected override void InitService(object service) + { + ClearDB(); + m_db = (TAssetData)service; + m_db.Initialise(m_connStr); + } + + private void ClearDB() + { + DropTables("assets"); + ResetMigrations("AssetStore"); + } + + + [Test] + public void T001_LoadEmpty() + { + TestHelpers.InMethod(); + + bool[] exist = m_db.AssetsExist(new[] { uuid1, uuid2, uuid3 }); + Assert.IsFalse(exist[0]); + Assert.IsFalse(exist[1]); + Assert.IsFalse(exist[2]); + } + + [Test] + public void T010_StoreReadVerifyAssets() + { + TestHelpers.InMethod(); + + AssetBase a1 = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, critter1.ToString()); + AssetBase a2 = new AssetBase(uuid2, "asset two", (sbyte)AssetType.Texture, critter2.ToString()); + AssetBase a3 = new AssetBase(uuid3, "asset three", (sbyte)AssetType.Texture, critter3.ToString()); + a1.Data = data1; + a2.Data = data1; + a3.Data = data1; + + scrambler.Scramble(a1); + scrambler.Scramble(a2); + scrambler.Scramble(a3); + + m_db.StoreAsset(a1); + m_db.StoreAsset(a2); + m_db.StoreAsset(a3); + a1.UploadAttempts = 0; + a2.UploadAttempts = 0; + a3.UploadAttempts = 0; + + AssetBase a1a = m_db.GetAsset(uuid1); + a1a.UploadAttempts = 0; + Assert.That(a1a, Constraints.PropertyCompareConstraint(a1)); + + AssetBase a2a = m_db.GetAsset(uuid2); + a2a.UploadAttempts = 0; + Assert.That(a2a, Constraints.PropertyCompareConstraint(a2)); + + AssetBase a3a = m_db.GetAsset(uuid3); + a3a.UploadAttempts = 0; + Assert.That(a3a, Constraints.PropertyCompareConstraint(a3)); + + scrambler.Scramble(a1a); + scrambler.Scramble(a2a); + scrambler.Scramble(a3a); + + m_db.StoreAsset(a1a); + m_db.StoreAsset(a2a); + m_db.StoreAsset(a3a); + a1a.UploadAttempts = 0; + a2a.UploadAttempts = 0; + a3a.UploadAttempts = 0; + + AssetBase a1b = m_db.GetAsset(uuid1); + a1b.UploadAttempts = 0; + Assert.That(a1b, Constraints.PropertyCompareConstraint(a1a)); + + AssetBase a2b = m_db.GetAsset(uuid2); + a2b.UploadAttempts = 0; + Assert.That(a2b, Constraints.PropertyCompareConstraint(a2a)); + + AssetBase a3b = m_db.GetAsset(uuid3); + a3b.UploadAttempts = 0; + Assert.That(a3b, Constraints.PropertyCompareConstraint(a3a)); + + bool[] exist = m_db.AssetsExist(new[] { uuid1, uuid2, uuid3 }); + Assert.IsTrue(exist[0]); + Assert.IsTrue(exist[1]); + Assert.IsTrue(exist[2]); + + List metadatas = m_db.FetchAssetMetadataSet(0, 1000); + + Assert.That(metadatas.Count >= 3, "FetchAssetMetadataSet() should have returned at least 3 assets!"); + + // It is possible that the Asset table is filled with data, in which case we don't try to find "our" + // assets there: + if (metadatas.Count < 1000) + { + AssetMetadata metadata = metadatas.Find(x => x.FullID == uuid1); + Assert.That(metadata.Name, Is.EqualTo(a1b.Name)); + Assert.That(metadata.Description, Is.EqualTo(a1b.Description)); + Assert.That(metadata.Type, Is.EqualTo(a1b.Type)); + Assert.That(metadata.Temporary, Is.EqualTo(a1b.Temporary)); + Assert.That(metadata.FullID, Is.EqualTo(a1b.FullID)); + } + } + + [Test] + public void T020_CheckForWeirdCreatorID() + { + TestHelpers.InMethod(); + + // It is expected that eventually the CreatorID might be an arbitrary string (an URI) + // rather than a valid UUID (?). This test is to make sure that the database layer does not + // attempt to convert CreatorID to GUID, but just passes it both ways as a string. + AssetBase a1 = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, critter1); + AssetBase a2 = new AssetBase(uuid2, "asset two", (sbyte)AssetType.Texture, "This is not a GUID!"); + AssetBase a3 = new AssetBase(uuid3, "asset three", (sbyte)AssetType.Texture, ""); + a1.Data = data1; + a2.Data = data1; + a3.Data = data1; + + m_db.StoreAsset(a1); + a1.UploadAttempts = 0; + m_db.StoreAsset(a2); + a2.UploadAttempts = 0; + m_db.StoreAsset(a3); + a3.UploadAttempts = 0; + + AssetBase a1a = m_db.GetAsset(uuid1); + a1a.UploadAttempts = 0; + Assert.That(a1a, Constraints.PropertyCompareConstraint(a1)); + + AssetBase a2a = m_db.GetAsset(uuid2); + a2a.UploadAttempts = 0; + Assert.That(a2a, Constraints.PropertyCompareConstraint(a2)); + + AssetBase a3a = m_db.GetAsset(uuid3); + a3a.UploadAttempts = 0; + Assert.That(a3a, Constraints.PropertyCompareConstraint(a3)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Data.Tests/BasicDataServiceTest.cs b/Tests/OpenSim.Data.Tests/BasicDataServiceTest.cs new file mode 100644 index 00000000000..86b37a8d33f --- /dev/null +++ b/Tests/OpenSim.Data.Tests/BasicDataServiceTest.cs @@ -0,0 +1,264 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Data; +using System.Data.Common; + +using Microsoft.Extensions.Logging; + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Data.Tests +{ + /// This is a base class for testing any Data service for any DBMS. + /// Requires NUnit 2.5 or better (to support the generics). + /// + /// + /// FIXME: Should extend OpenSimTestCase but compile on mono 2.4.3 currently fails with + /// AssetTests`2 : System.MemberAccessException : Cannot create an instance of OpenSim.Data.Tests.AssetTests`2[TConn,TAssetData] because Type.ContainsGenericParameters is true. + /// and similar on EstateTests, InventoryTests and RegionTests. + /// Runs fine with mono 2.10.8.1, so easiest thing is to wait until min Mono version uplifts. + /// + /// + /// + public class BasicDataServiceTest + where TConn : DbConnection, new() + where TService : class, new() + { + protected string m_connStr; + private TService m_service; + private string m_file; + + // TODO: Is this in the right place here? + // Later: apparently it's not, but does it matter here? +// protected static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected ILogger m_logger; // doesn't matter here that it's not static, init to correct type in instance .ctor + + public BasicDataServiceTest() + : this("") + { + } + + public BasicDataServiceTest(string conn) + { + m_connStr = !String.IsNullOrEmpty(conn) ? conn : DefaultTestConns.Get(typeof(TConn)); + + m_logger ??= LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger>(); + } + + /// + /// To be overridden in derived classes. Do whatever init with the m_service, like setting the conn string to it. + /// You'd probably want to to cast the 'service' to a more specific type and store it in a member var. + /// This framework takes care of disposing it, if it's disposable. + /// + /// The service being tested + protected virtual void InitService(object service) + { + } + + [OneTimeSetUp] + public void Init() + { + // Sorry, some SQLite-specific stuff goes here (not a big deal, as its just some file ops) + if (typeof(TConn).Name.StartsWith("Sqlite")) + { + // SQLite doesn't work on power or z linux + if (Directory.Exists("/proc/ppc64") || Directory.Exists("/proc/dasd")) + Assert.Ignore(); + + if (Util.IsWindows()) + Util.LoadArchSpecificWindowsDll("sqlite3.dll"); + + // for SQLite, if no explicit conn string is specified, use a temp file + if (String.IsNullOrEmpty(m_connStr)) + { + m_file = Path.GetTempFileName() + ".db"; + m_connStr = "URI=file:" + m_file + ",version=3"; + } + } + + if (String.IsNullOrEmpty(m_connStr)) + { + string msg = String.Format("Connection string for {0} is not defined, ignoring tests", typeof(TConn).Name); + m_logger?.LogWarning(msg); + Assert.Ignore(msg); + } + + // Try the connection, ignore tests if Open() fails + using (TConn conn = new TConn()) + { + conn.ConnectionString = m_connStr; + try + { + conn.Open(); + conn.Close(); + } + catch + { + string msg = String.Format("{0} is unable to connect to the database, ignoring tests", typeof(TConn).Name); + m_logger?.LogWarning(msg); + Assert.Ignore(msg); + } + } + + // If we manage to connect to the database with the user + // and password above it is our test database, and run + // these tests. If anything goes wrong, ignore these + // tests. + try + { + m_service = new TService(); + InitService(m_service); + } + catch (Exception e) + { + m_logger?.LogError(e.ToString()); + Assert.Ignore(); + } + } + + [OneTimeTearDown] + public void Cleanup() + { + if (m_service != null) + { + if (m_service is IDisposable) + ((IDisposable)m_service).Dispose(); + m_service = null; + } + + if (!String.IsNullOrEmpty(m_file) && File.Exists(m_file)) + File.Delete(m_file); + } + + protected virtual DbConnection Connect() + { + DbConnection cnn = new TConn(); + cnn.ConnectionString = m_connStr; + cnn.Open(); + return cnn; + } + + protected virtual void ExecuteSql(string sql) + { + using (DbConnection dbcon = Connect()) + { + using (DbCommand cmd = dbcon.CreateCommand()) + { + cmd.CommandText = sql; + cmd.ExecuteNonQuery(); + } + } + } + + protected delegate bool ProcessRow(IDataReader reader); + + protected virtual int ExecQuery(string sql, bool bSingleRow, ProcessRow action) + { + int nRecs = 0; + using (DbConnection dbcon = Connect()) + { + using (DbCommand cmd = dbcon.CreateCommand()) + { + cmd.CommandText = sql; + CommandBehavior cb = bSingleRow ? CommandBehavior.SingleRow : CommandBehavior.Default; + using (DbDataReader rdr = cmd.ExecuteReader(cb)) + { + while (rdr.Read()) + { + nRecs++; + if (!action(rdr)) + break; + } + } + } + } + return nRecs; + } + + /// Drop tables (listed as parameters). There is no "DROP IF EXISTS" syntax common for all + /// databases, so we just DROP and ignore an exception. + /// + /// + protected virtual void DropTables(params string[] tables) + { + foreach (string tbl in tables) + { + try + { + ExecuteSql("DROP TABLE " + tbl + ";"); + }catch + { + } + } + } + + /// Clear tables listed as parameters (without dropping them). + /// + /// + protected virtual void ResetMigrations(params string[] stores) + { + string lst = ""; + foreach (string store in stores) + { + string s = "'" + store + "'"; + if (lst.Length == 0) + lst = s; + else + lst += ", " + s; + } + + string sCond = stores.Length > 1 ? ("in (" + lst + ")") : ("=" + lst); + try + { + ExecuteSql("DELETE FROM migrations where name " + sCond); + } + catch + { + } + } + + /// Clear tables listed as parameters (without dropping them). + /// + /// + protected virtual void ClearTables(params string[] tables) + { + foreach (string tbl in tables) + { + try + { + ExecuteSql("DELETE FROM " + tbl + ";"); + } + catch + { + } + } + } + } +} diff --git a/Tests/OpenSim.Data.Tests/DataTestUtil.cs b/Tests/OpenSim.Data.Tests/DataTestUtil.cs new file mode 100644 index 00000000000..5393529592d --- /dev/null +++ b/Tests/OpenSim.Data.Tests/DataTestUtil.cs @@ -0,0 +1,88 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using OpenMetaverse; +using NUnit.Framework; + +namespace OpenSim.Data.Tests +{ + /// + /// Shared constants and methods for database unit tests. + /// + public class DataTestUtil + { + public const uint UNSIGNED_INTEGER_MIN = uint.MinValue; + //public const uint UNSIGNED_INTEGER_MAX = uint.MaxValue; + public const uint UNSIGNED_INTEGER_MAX = INTEGER_MAX; + + public const int INTEGER_MIN = int.MinValue + 1; // Postgresql requires +1 to .NET int.MinValue + public const int INTEGER_MAX = int.MaxValue; + + public const float FLOAT_MIN = float.MinValue * (1 - FLOAT_PRECISSION); + public const float FLOAT_MAX = float.MaxValue * (1 - FLOAT_PRECISSION); + public const float FLOAT_ACCURATE = 1.234567890123456789012f; + public const float FLOAT_PRECISSION = 1E-5f; // Native MySQL is severly limited with floating accuracy + + public const double DOUBLE_MIN = -1E52 * (1 - DOUBLE_PRECISSION); + public const double DOUBLE_MAX = 1E52 * (1 - DOUBLE_PRECISSION); + public const double DOUBLE_ACCURATE = 1.2345678901234567890123456789012345678901234567890123f; + public const double DOUBLE_PRECISSION = 1E-14; // Native MySQL is severly limited with double accuracy + + public const string STRING_MIN = ""; + public static string STRING_MAX(int length) + { + StringBuilder stringBuilder = new StringBuilder(); + for (int i = 0; i < length; i++) + { + stringBuilder.Append(i % 10); + } + return stringBuilder.ToString(); + } + + public static UUID UUID_MIN = new UUID("00000000-0000-0000-0000-000000000000"); + public static UUID UUID_MAX = new UUID("ffffffff-ffff-ffff-ffff-ffffffffffff"); + + public const bool BOOLEAN_MIN = false; + public const bool BOOLEAN_MAX = true; + + public static void AssertFloatEqualsWithTolerance(float expectedValue, float actualValue) + { + Assert.GreaterOrEqual(actualValue, expectedValue - Math.Abs(expectedValue) * FLOAT_PRECISSION); + Assert.LessOrEqual(actualValue, expectedValue + Math.Abs(expectedValue) * FLOAT_PRECISSION); + } + + public static void AssertDoubleEqualsWithTolerance(double expectedValue, double actualValue) + { + Assert.GreaterOrEqual(actualValue, expectedValue - Math.Abs(expectedValue) * DOUBLE_PRECISSION); + Assert.LessOrEqual(actualValue, expectedValue + Math.Abs(expectedValue) * DOUBLE_PRECISSION); + } + } +} + diff --git a/Tests/OpenSim.Data.Tests/DefaultTestConns.cs b/Tests/OpenSim.Data.Tests/DefaultTestConns.cs new file mode 100644 index 00000000000..86297f8a328 --- /dev/null +++ b/Tests/OpenSim.Data.Tests/DefaultTestConns.cs @@ -0,0 +1,84 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Reflection; + +namespace OpenSim.Data.Tests +{ + /// This static class looks for TestDataConnections.ini file in the /bin directory to obtain + /// a connection string for testing one of the supported databases. + /// The connections must be in the section [TestConnections] with names matching the connection class + /// name for the specific database, e.g.: + /// + /// [TestConnections] + /// MySqlConnection="..." + /// SqlConnection="..." + /// SQLiteConnection="..." + /// + /// Note that the conn string may also be set explicitly in the [TestCase()] attribute of test classes + /// based on BasicDataServiceTest.cs. + /// + + static class DefaultTestConns + { + private static Dictionary conns = new Dictionary(); + + public static string Get(Type connType) + { + string sConn; + + if (conns.TryGetValue(connType, out sConn)) + return sConn; + + Assembly asm = Assembly.GetExecutingAssembly(); + string sType = connType.Name; + + // Note: when running from NUnit, the DLL is located in some temp dir, so how do we get + // to the INI file? Ok, so put it into the resources! + // string iniName = Path.Combine(Path.GetDirectoryName(asm.Location), "TestDataConnections.ini"); + + string[] allres = asm.GetManifestResourceNames(); + string sResFile = Array.Find(allres, s => s.Contains("TestDataConnections.ini")); + + if (String.IsNullOrEmpty(sResFile)) + throw new Exception(String.Format("Please add resource TestDataConnections.ini, with section [TestConnections] and settings like {0}=\"...\"", + sType)); + + using (Stream resource = asm.GetManifestResourceStream(sResFile)) + { + IConfigSource source = new IniConfigSource(resource); + var cfg = source.Configs["TestConnections"]; + sConn = cfg.Get(sType, ""); + } + + if (!String.IsNullOrEmpty(sConn)) + conns[connType] = sConn; + + return sConn; + } + } +} diff --git a/Tests/OpenSim.Data.Tests/EstateTests.cs b/Tests/OpenSim.Data.Tests/EstateTests.cs new file mode 100644 index 00000000000..4bfdc64f1da --- /dev/null +++ b/Tests/OpenSim.Data.Tests/EstateTests.cs @@ -0,0 +1,511 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common; +using System.Data.Common; + +// DBMS-specific: +using OpenSim.Data.MySQL; + +using System.Data.SQLite; +using OpenSim.Data.SQLite; +using MySqlConnector; + +namespace OpenSim.Data.Tests +{ + [TestFixture(Description = "Estate store tests (SQLite)")] + public class SQLiteEstateTests : EstateTests + { + } + + [TestFixture(Description = "Estate store tests (MySQL)")] + public class MySqlEstateTests : EstateTests + { + } + + public class EstateTests : BasicDataServiceTest + where TConn : DbConnection, new() + where TEstateStore : class, IEstateDataStore, new() + { + public IEstateDataStore db; + + public static UUID REGION_ID = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed7"); + + public static UUID USER_ID_1 = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed1"); + public static UUID USER_ID_2 = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed2"); + + public static UUID MANAGER_ID_1 = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed3"); + public static UUID MANAGER_ID_2 = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed4"); + + public static UUID GROUP_ID_1 = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed5"); + public static UUID GROUP_ID_2 = new UUID("250d214e-1c7e-4f9b-a488-87c5e53feed6"); + + protected override void InitService(object service) + { + ClearDB(); + db = (IEstateDataStore)service; + db.Initialise(m_connStr); + } + + private void ClearDB() + { + // if a new table is added, it has to be dropped here + DropTables( + "estate_managers", + "estate_groups", + "estate_users", + "estateban", + "estate_settings", + "estate_map" + ); + ResetMigrations("EstateStore"); + } + + #region 0Tests + + [Test] + public void T010_EstateSettingsSimpleStorage_MinimumParameterSet() + { + TestHelpers.InMethod(); + + EstateSettingsSimpleStorage( + REGION_ID, + DataTestUtil.STRING_MIN, + DataTestUtil.UNSIGNED_INTEGER_MIN, + DataTestUtil.FLOAT_MIN, + DataTestUtil.INTEGER_MIN, + DataTestUtil.INTEGER_MIN, + DataTestUtil.INTEGER_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.DOUBLE_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.STRING_MIN, + DataTestUtil.UUID_MIN + ); + } + + [Test] + public void T011_EstateSettingsSimpleStorage_MaximumParameterSet() + { + TestHelpers.InMethod(); + + EstateSettingsSimpleStorage( + REGION_ID, + DataTestUtil.STRING_MAX(64), + DataTestUtil.UNSIGNED_INTEGER_MAX, + DataTestUtil.FLOAT_MAX, + DataTestUtil.INTEGER_MAX, + DataTestUtil.INTEGER_MAX, + DataTestUtil.INTEGER_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.DOUBLE_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.BOOLEAN_MAX, + DataTestUtil.STRING_MAX(255), + DataTestUtil.UUID_MAX + ); + } + + [Test] + public void T012_EstateSettingsSimpleStorage_AccurateParameterSet() + { + TestHelpers.InMethod(); + + EstateSettingsSimpleStorage( + REGION_ID, + DataTestUtil.STRING_MAX(1), + DataTestUtil.UNSIGNED_INTEGER_MIN, + DataTestUtil.FLOAT_ACCURATE, + DataTestUtil.INTEGER_MIN, + DataTestUtil.INTEGER_MIN, + DataTestUtil.INTEGER_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.DOUBLE_ACCURATE, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.BOOLEAN_MIN, + DataTestUtil.STRING_MAX(1), + DataTestUtil.UUID_MIN + ); + } + + [Test] + public void T012_EstateSettingsRandomStorage() + { + TestHelpers.InMethod(); + + // Letting estate store generate rows to database for us + EstateSettings originalSettings = db.LoadEstateSettings(REGION_ID, true); + new PropertyScrambler() + .DontScramble(x=>x.EstateID) + .Scramble(originalSettings); + + // Saving settings. + db.StoreEstateSettings(originalSettings); + + // Loading settings to another instance variable. + EstateSettings loadedSettings = db.LoadEstateSettings(REGION_ID, true); + + // Checking that loaded values are correct. + Assert.That(loadedSettings, Constraints.PropertyCompareConstraint(originalSettings)); + } + + [Test] + public void T020_EstateSettingsManagerList() + { + TestHelpers.InMethod(); + + // Letting estate store generate rows to database for us + EstateSettings originalSettings = db.LoadEstateSettings(REGION_ID, true); + + originalSettings.EstateManagers = new UUID[] { MANAGER_ID_1, MANAGER_ID_2 }; + + // Saving settings. + db.StoreEstateSettings(originalSettings); + + // Loading settings to another instance variable. + EstateSettings loadedSettings = db.LoadEstateSettings(REGION_ID, true); + + Assert.AreEqual(2, loadedSettings.EstateManagers.Length); + Assert.AreEqual(MANAGER_ID_1, loadedSettings.EstateManagers[0]); + Assert.AreEqual(MANAGER_ID_2, loadedSettings.EstateManagers[1]); + } + + [Test] + public void T021_EstateSettingsUserList() + { + TestHelpers.InMethod(); + + // Letting estate store generate rows to database for us + EstateSettings originalSettings = db.LoadEstateSettings(REGION_ID, true); + + originalSettings.EstateAccess = new UUID[] { USER_ID_1, USER_ID_2 }; + + // Saving settings. + db.StoreEstateSettings(originalSettings); + + // Loading settings to another instance variable. + EstateSettings loadedSettings = db.LoadEstateSettings(REGION_ID, true); + + Assert.AreEqual(2, loadedSettings.EstateAccess.Length); + Assert.AreEqual(USER_ID_1, loadedSettings.EstateAccess[0]); + Assert.AreEqual(USER_ID_2, loadedSettings.EstateAccess[1]); + } + + [Test] + public void T022_EstateSettingsGroupList() + { + TestHelpers.InMethod(); + + // Letting estate store generate rows to database for us + EstateSettings originalSettings = db.LoadEstateSettings(REGION_ID, true); + + originalSettings.EstateGroups = new UUID[] { GROUP_ID_1, GROUP_ID_2 }; + + // Saving settings. + db.StoreEstateSettings(originalSettings); + + // Loading settings to another instance variable. + EstateSettings loadedSettings = db.LoadEstateSettings(REGION_ID, true); + + Assert.AreEqual(2, loadedSettings.EstateAccess.Length); + Assert.AreEqual(GROUP_ID_1, loadedSettings.EstateGroups[0]); + Assert.AreEqual(GROUP_ID_2, loadedSettings.EstateGroups[1]); + } + + [Test] + public void T022_EstateSettingsBanList() + { + TestHelpers.InMethod(); + + // Letting estate store generate rows to database for us + EstateSettings originalSettings = db.LoadEstateSettings(REGION_ID, true); + + EstateBan estateBan1 = new EstateBan(); + estateBan1.BannedUserID = DataTestUtil.UUID_MIN; + + EstateBan estateBan2 = new EstateBan(); + estateBan2.BannedUserID = DataTestUtil.UUID_MAX; + + originalSettings.EstateBans = new EstateBan[] { estateBan1, estateBan2 }; + + // Saving settings. + db.StoreEstateSettings(originalSettings); + + // Loading settings to another instance variable. + EstateSettings loadedSettings = db.LoadEstateSettings(REGION_ID, true); + + Assert.AreEqual(2, loadedSettings.EstateBans.Length); + Assert.AreEqual(DataTestUtil.UUID_MIN, loadedSettings.EstateBans[0].BannedUserID); + + Assert.AreEqual(DataTestUtil.UUID_MAX, loadedSettings.EstateBans[1].BannedUserID); + + } + + #endregion + + #region Parametrizable Test Implementations + + private void EstateSettingsSimpleStorage( + UUID regionId, + string estateName, + uint parentEstateID, + float billableFactor, + int pricePerMeter, + int redirectGridX, + int redirectGridY, + bool useGlobalTime, + bool fixedSun, + double sunPosition, + bool allowVoice, + bool allowDirectTeleport, + bool resetHomeOnTeleport, + bool denyAnonymous, + bool denyIdentified, + bool denyTransacted, + bool denyMinors, + bool abuseEmailToEstateOwner, + bool blockDwell, + bool estateSkipScripts, + bool taxFree, + bool publicAccess, + string abuseEmail, + UUID estateOwner + ) + { + + // Letting estate store generate rows to database for us + EstateSettings originalSettings = db.LoadEstateSettings(regionId, true); + + SetEstateSettings(originalSettings, + estateName, + parentEstateID, + billableFactor, + pricePerMeter, + redirectGridX, + redirectGridY, + useGlobalTime, + fixedSun, + sunPosition, + allowVoice, + allowDirectTeleport, + resetHomeOnTeleport, + denyAnonymous, + denyIdentified, + denyTransacted, + denyMinors, + abuseEmailToEstateOwner, + blockDwell, + estateSkipScripts, + taxFree, + publicAccess, + abuseEmail, + estateOwner + ); + + // Saving settings. + db.StoreEstateSettings(originalSettings); + + // Loading settings to another instance variable. + EstateSettings loadedSettings = db.LoadEstateSettings(regionId, true); + + // Checking that loaded values are correct. + ValidateEstateSettings(loadedSettings, + estateName, + parentEstateID, + billableFactor, + pricePerMeter, + redirectGridX, + redirectGridY, + useGlobalTime, + fixedSun, + sunPosition, + allowVoice, + allowDirectTeleport, + resetHomeOnTeleport, + denyAnonymous, + denyIdentified, + denyTransacted, + denyMinors, + abuseEmailToEstateOwner, + blockDwell, + estateSkipScripts, + taxFree, + publicAccess, + abuseEmail, + estateOwner + ); + + } + + #endregion + + #region EstateSetting Initialization and Validation Methods + + private void SetEstateSettings( + EstateSettings estateSettings, + string estateName, + uint parentEstateID, + float billableFactor, + int pricePerMeter, + int redirectGridX, + int redirectGridY, + bool useGlobalTime, + bool fixedSun, + double sunPosition, + bool allowVoice, + bool allowDirectTeleport, + bool resetHomeOnTeleport, + bool denyAnonymous, + bool denyIdentified, + bool denyTransacted, + bool denyMinors, + bool abuseEmailToEstateOwner, + bool blockDwell, + bool estateSkipScripts, + bool taxFree, + bool publicAccess, + string abuseEmail, + UUID estateOwner + ) + { + estateSettings.EstateName = estateName; + estateSettings.ParentEstateID = parentEstateID; + estateSettings.BillableFactor = billableFactor; + estateSettings.PricePerMeter = pricePerMeter; + estateSettings.RedirectGridX = redirectGridX; + estateSettings.RedirectGridY = redirectGridY; + estateSettings.UseGlobalTime = useGlobalTime; + estateSettings.FixedSun = fixedSun; + estateSettings.SunPosition = sunPosition; + estateSettings.AllowVoice = allowVoice; + estateSettings.AllowDirectTeleport = allowDirectTeleport; + estateSettings.ResetHomeOnTeleport = resetHomeOnTeleport; + estateSettings.DenyAnonymous = denyAnonymous; + estateSettings.DenyIdentified = denyIdentified; + estateSettings.DenyTransacted = denyTransacted; + estateSettings.DenyMinors = denyMinors; + estateSettings.AbuseEmailToEstateOwner = abuseEmailToEstateOwner; + estateSettings.BlockDwell = blockDwell; + estateSettings.EstateSkipScripts = estateSkipScripts; + estateSettings.TaxFree = taxFree; + estateSettings.PublicAccess = publicAccess; + estateSettings.AbuseEmail = abuseEmail; + estateSettings.EstateOwner = estateOwner; + } + + private void ValidateEstateSettings( + EstateSettings estateSettings, + string estateName, + uint parentEstateID, + float billableFactor, + int pricePerMeter, + int redirectGridX, + int redirectGridY, + bool useGlobalTime, + bool fixedSun, + double sunPosition, + bool allowVoice, + bool allowDirectTeleport, + bool resetHomeOnTeleport, + bool denyAnonymous, + bool denyIdentified, + bool denyTransacted, + bool denyMinors, + bool abuseEmailToEstateOwner, + bool blockDwell, + bool estateSkipScripts, + bool taxFree, + bool publicAccess, + string abuseEmail, + UUID estateOwner + ) + { + Assert.AreEqual(estateName, estateSettings.EstateName); + Assert.AreEqual(parentEstateID, estateSettings.ParentEstateID); + + DataTestUtil.AssertFloatEqualsWithTolerance(billableFactor, estateSettings.BillableFactor); + + Assert.AreEqual(pricePerMeter, estateSettings.PricePerMeter); + Assert.AreEqual(redirectGridX, estateSettings.RedirectGridX); + Assert.AreEqual(redirectGridY, estateSettings.RedirectGridY); + + Assert.AreEqual(allowVoice, estateSettings.AllowVoice); + Assert.AreEqual(allowDirectTeleport, estateSettings.AllowDirectTeleport); + Assert.AreEqual(resetHomeOnTeleport, estateSettings.ResetHomeOnTeleport); + Assert.AreEqual(denyAnonymous, estateSettings.DenyAnonymous); + Assert.AreEqual(denyIdentified, estateSettings.DenyIdentified); + Assert.AreEqual(denyTransacted, estateSettings.DenyTransacted); + Assert.AreEqual(denyMinors, estateSettings.DenyMinors); + Assert.AreEqual(abuseEmailToEstateOwner, estateSettings.AbuseEmailToEstateOwner); + Assert.AreEqual(blockDwell, estateSettings.BlockDwell); + Assert.AreEqual(estateSkipScripts, estateSettings.EstateSkipScripts); + Assert.AreEqual(taxFree, estateSettings.TaxFree); + Assert.AreEqual(publicAccess, estateSettings.PublicAccess); + Assert.AreEqual(abuseEmail, estateSettings.AbuseEmail); + Assert.AreEqual(estateOwner, estateSettings.EstateOwner); + } + + #endregion + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Data.Tests/InventoryTests.cs b/Tests/OpenSim.Data.Tests/InventoryTests.cs new file mode 100644 index 00000000000..dc99bf1a5a3 --- /dev/null +++ b/Tests/OpenSim.Data.Tests/InventoryTests.cs @@ -0,0 +1,369 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common; +using System.Data.Common; + +// DBMS-specific: +using OpenSim.Data.MySQL; +using MySqlConnector; + +namespace OpenSim.Data.Tests +{ + [TestFixture(Description = "Inventory store tests (MySQL)")] + public class MySqlInventoryTests : InventoryTests + { + } + + public class InventoryTests : BasicDataServiceTest + where TConn : DbConnection, new() + where TInvStore : class, IInventoryDataPlugin, new() + { + public IInventoryDataPlugin db; + + public UUID zero = UUID.Zero; + + public UUID folder1 = UUID.Random(); + public UUID folder2 = UUID.Random(); + public UUID folder3 = UUID.Random(); + public UUID owner1 = UUID.Random(); + public UUID owner2 = UUID.Random(); + public UUID owner3 = UUID.Random(); + + public UUID item1 = UUID.Random(); + public UUID item2 = UUID.Random(); + public UUID item3 = UUID.Random(); + public UUID asset1 = UUID.Random(); + public UUID asset2 = UUID.Random(); + public UUID asset3 = UUID.Random(); + + public string name1; + public string name2 = "First Level folder"; + public string name3 = "First Level folder 2"; + public string niname1 = "My Shirt"; + public string iname1 = "Shirt"; + public string iname2 = "Text Board"; + public string iname3 = "No Pants Barrel"; + + public InventoryTests(string conn) : base(conn) + { + name1 = "Root Folder for " + owner1.ToString(); + } + public InventoryTests() : this("") { } + + protected override void InitService(object service) + { + ClearDB(); + db = (IInventoryDataPlugin)service; + db.Initialise(m_connStr); + } + + private void ClearDB() + { + DropTables("inventoryitems", "inventoryfolders"); + ResetMigrations("InventoryStore"); + } + + [Test] + public void T001_LoadEmpty() + { + TestHelpers.InMethod(); + + Assert.That(db.getInventoryFolder(zero), Is.Null); + Assert.That(db.getInventoryFolder(folder1), Is.Null); + Assert.That(db.getInventoryFolder(folder2), Is.Null); + Assert.That(db.getInventoryFolder(folder3), Is.Null); + + Assert.That(db.getInventoryItem(zero), Is.Null); + Assert.That(db.getInventoryItem(item1), Is.Null); + Assert.That(db.getInventoryItem(item2), Is.Null); + Assert.That(db.getInventoryItem(item3), Is.Null); + + Assert.That(db.getUserRootFolder(zero), Is.Null); + Assert.That(db.getUserRootFolder(owner1), Is.Null); + } + + // 01x - folder tests + [Test] + public void T010_FolderNonParent() + { + TestHelpers.InMethod(); + + InventoryFolderBase f1 = NewFolder(folder2, folder1, owner1, name2); + // the folder will go in + db.addInventoryFolder(f1); + InventoryFolderBase f1a = db.getUserRootFolder(owner1); + Assert.That(f1a, Is.Null); + } + + [Test] + public void T011_FolderCreate() + { + TestHelpers.InMethod(); + + InventoryFolderBase f1 = NewFolder(folder1, zero, owner1, name1); + // TODO: this is probably wrong behavior, but is what we have + // db.updateInventoryFolder(f1); + // InventoryFolderBase f1a = db.getUserRootFolder(owner1); + // Assert.That(uuid1, Is.EqualTo(f1a.ID)) + // Assert.That(name1, Text.Matches(f1a.Name), "Assert.That(name1, Text.Matches(f1a.Name))"); + // Assert.That(db.getUserRootFolder(owner1), Is.Null); + + // succeed with true + db.addInventoryFolder(f1); + InventoryFolderBase f1a = db.getUserRootFolder(owner1); + Assert.That(folder1, Is.EqualTo(f1a.ID), "Assert.That(folder1, Is.EqualTo(f1a.ID))"); + Assert.That(name1, Does.Match(f1a.Name), "Assert.That(name1, Text.Matches(f1a.Name))"); + } + + // we now have the following tree + // folder1 + // +--- folder2 + // +--- folder3 + + [Test] + public void T012_FolderList() + { + TestHelpers.InMethod(); + + InventoryFolderBase f2 = NewFolder(folder3, folder1, owner1, name3); + db.addInventoryFolder(f2); + + Assert.That(db.getInventoryFolders(zero).Count, Is.EqualTo(1), "Assert.That(db.getInventoryFolders(zero).Count, Is.EqualTo(1))"); + Assert.That(db.getInventoryFolders(folder1).Count, Is.EqualTo(2), "Assert.That(db.getInventoryFolders(folder1).Count, Is.EqualTo(2))"); + Assert.That(db.getInventoryFolders(folder2).Count, Is.EqualTo(0), "Assert.That(db.getInventoryFolders(folder2).Count, Is.EqualTo(0))"); + Assert.That(db.getInventoryFolders(folder3).Count, Is.EqualTo(0), "Assert.That(db.getInventoryFolders(folder3).Count, Is.EqualTo(0))"); + Assert.That(db.getInventoryFolders(UUID.Random()).Count, Is.EqualTo(0), "Assert.That(db.getInventoryFolders(UUID.Random()).Count, Is.EqualTo(0))"); + + } + + [Test] + public void T013_FolderHierarchy() + { + TestHelpers.InMethod(); + + int n = db.getFolderHierarchy(zero).Count; // (for dbg - easier to see what's returned) + Assert.That(n, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(zero).Count, Is.EqualTo(0))"); + n = db.getFolderHierarchy(folder1).Count; + Assert.That(n, Is.EqualTo(2), "Assert.That(db.getFolderHierarchy(folder1).Count, Is.EqualTo(2))"); + Assert.That(db.getFolderHierarchy(folder2).Count, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(folder2).Count, Is.EqualTo(0))"); + Assert.That(db.getFolderHierarchy(folder3).Count, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(folder3).Count, Is.EqualTo(0))"); + Assert.That(db.getFolderHierarchy(UUID.Random()).Count, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(UUID.Random()).Count, Is.EqualTo(0))"); + } + + + [Test] + public void T014_MoveFolder() + { + TestHelpers.InMethod(); + + InventoryFolderBase f2 = db.getInventoryFolder(folder2); + f2.ParentID = folder3; + db.moveInventoryFolder(f2); + + Assert.That(db.getInventoryFolders(zero).Count, Is.EqualTo(1), "Assert.That(db.getInventoryFolders(zero).Count, Is.EqualTo(1))"); + Assert.That(db.getInventoryFolders(folder1).Count, Is.EqualTo(1), "Assert.That(db.getInventoryFolders(folder1).Count, Is.EqualTo(1))"); + Assert.That(db.getInventoryFolders(folder2).Count, Is.EqualTo(0), "Assert.That(db.getInventoryFolders(folder2).Count, Is.EqualTo(0))"); + Assert.That(db.getInventoryFolders(folder3).Count, Is.EqualTo(1), "Assert.That(db.getInventoryFolders(folder3).Count, Is.EqualTo(1))"); + Assert.That(db.getInventoryFolders(UUID.Random()).Count, Is.EqualTo(0), "Assert.That(db.getInventoryFolders(UUID.Random()).Count, Is.EqualTo(0))"); + } + + [Test] + public void T015_FolderHierarchy() + { + TestHelpers.InMethod(); + + Assert.That(db.getFolderHierarchy(zero).Count, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(zero).Count, Is.EqualTo(0))"); + Assert.That(db.getFolderHierarchy(folder1).Count, Is.EqualTo(2), "Assert.That(db.getFolderHierarchy(folder1).Count, Is.EqualTo(2))"); + Assert.That(db.getFolderHierarchy(folder2).Count, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(folder2).Count, Is.EqualTo(0))"); + Assert.That(db.getFolderHierarchy(folder3).Count, Is.EqualTo(1), "Assert.That(db.getFolderHierarchy(folder3).Count, Is.EqualTo(1))"); + Assert.That(db.getFolderHierarchy(UUID.Random()).Count, Is.EqualTo(0), "Assert.That(db.getFolderHierarchy(UUID.Random()).Count, Is.EqualTo(0))"); + } + + // Item tests + [Test] + public void T100_NoItems() + { + TestHelpers.InMethod(); + + Assert.That(db.getInventoryInFolder(zero).Count, Is.EqualTo(0), "Assert.That(db.getInventoryInFolder(zero).Count, Is.EqualTo(0))"); + Assert.That(db.getInventoryInFolder(folder1).Count, Is.EqualTo(0), "Assert.That(db.getInventoryInFolder(folder1).Count, Is.EqualTo(0))"); + Assert.That(db.getInventoryInFolder(folder2).Count, Is.EqualTo(0), "Assert.That(db.getInventoryInFolder(folder2).Count, Is.EqualTo(0))"); + Assert.That(db.getInventoryInFolder(folder3).Count, Is.EqualTo(0), "Assert.That(db.getInventoryInFolder(folder3).Count, Is.EqualTo(0))"); + } + + // TODO: Feeding a bad inventory item down the data path will + // crash the system. This is largely due to the builder + // routines. That should be fixed and tested for. + [Test] + public void T101_CreatItems() + { + TestHelpers.InMethod(); + + db.addInventoryItem(NewItem(item1, folder3, owner1, iname1, asset1)); + db.addInventoryItem(NewItem(item2, folder3, owner1, iname2, asset2)); + db.addInventoryItem(NewItem(item3, folder3, owner1, iname3, asset3)); + Assert.That(db.getInventoryInFolder(folder3).Count, Is.EqualTo(3), "Assert.That(db.getInventoryInFolder(folder3).Count, Is.EqualTo(3))"); + } + + [Test] + public void T102_CompareItems() + { + TestHelpers.InMethod(); + + InventoryItemBase i1 = db.getInventoryItem(item1); + InventoryItemBase i2 = db.getInventoryItem(item2); + InventoryItemBase i3 = db.getInventoryItem(item3); + Assert.That(i1.Name, Is.EqualTo(iname1), "Assert.That(i1.Name, Is.EqualTo(iname1))"); + Assert.That(i2.Name, Is.EqualTo(iname2), "Assert.That(i2.Name, Is.EqualTo(iname2))"); + Assert.That(i3.Name, Is.EqualTo(iname3), "Assert.That(i3.Name, Is.EqualTo(iname3))"); + Assert.That(i1.Owner, Is.EqualTo(owner1), "Assert.That(i1.Owner, Is.EqualTo(owner1))"); + Assert.That(i2.Owner, Is.EqualTo(owner1), "Assert.That(i2.Owner, Is.EqualTo(owner1))"); + Assert.That(i3.Owner, Is.EqualTo(owner1), "Assert.That(i3.Owner, Is.EqualTo(owner1))"); + Assert.That(i1.AssetID, Is.EqualTo(asset1), "Assert.That(i1.AssetID, Is.EqualTo(asset1))"); + Assert.That(i2.AssetID, Is.EqualTo(asset2), "Assert.That(i2.AssetID, Is.EqualTo(asset2))"); + Assert.That(i3.AssetID, Is.EqualTo(asset3), "Assert.That(i3.AssetID, Is.EqualTo(asset3))"); + } + + [Test] + public void T103_UpdateItem() + { + TestHelpers.InMethod(); + + // TODO: probably shouldn't have the ability to have an + // owner of an item in a folder not owned by the user + + InventoryItemBase i1 = db.getInventoryItem(item1); + i1.Name = niname1; + i1.Description = niname1; + i1.Owner = owner2; + db.updateInventoryItem(i1); + + i1 = db.getInventoryItem(item1); + Assert.That(i1.Name, Is.EqualTo(niname1), "Assert.That(i1.Name, Is.EqualTo(niname1))"); + Assert.That(i1.Description, Is.EqualTo(niname1), "Assert.That(i1.Description, Is.EqualTo(niname1))"); + Assert.That(i1.Owner, Is.EqualTo(owner2), "Assert.That(i1.Owner, Is.EqualTo(owner2))"); + } + + [Test] + public void T104_RandomUpdateItem() + { + TestHelpers.InMethod(); + + PropertyScrambler folderScrambler = + new PropertyScrambler() + .DontScramble(x => x.Owner) + .DontScramble(x => x.ParentID) + .DontScramble(x => x.ID); + UUID owner = UUID.Random(); + UUID folder = UUID.Random(); + UUID rootId = UUID.Random(); + UUID rootAsset = UUID.Random(); + InventoryFolderBase f1 = NewFolder(folder, zero, owner, name1); + folderScrambler.Scramble(f1); + + db.addInventoryFolder(f1); + InventoryFolderBase f1a = db.getUserRootFolder(owner); + Assert.That(f1a, Constraints.PropertyCompareConstraint(f1)); + + folderScrambler.Scramble(f1a); + + db.updateInventoryFolder(f1a); + + InventoryFolderBase f1b = db.getUserRootFolder(owner); + Assert.That(f1b, Constraints.PropertyCompareConstraint(f1a)); + + //Now we have a valid folder to insert into, we can insert the item. + PropertyScrambler inventoryScrambler = + new PropertyScrambler() + .DontScramble(x => x.ID) + .DontScramble(x => x.AssetID) + .DontScramble(x => x.Owner) + .DontScramble(x => x.Folder); + InventoryItemBase root = NewItem(rootId, folder, owner, iname1, rootAsset); + inventoryScrambler.Scramble(root); + db.addInventoryItem(root); + + InventoryItemBase expected = db.getInventoryItem(rootId); + Assert.That(expected, Constraints.PropertyCompareConstraint(root) + .IgnoreProperty(x => x.InvType) + .IgnoreProperty(x => x.CreatorIdAsUuid) + .IgnoreProperty(x => x.Description) + .IgnoreProperty(x => x.CreatorIdentification) + .IgnoreProperty(x => x.CreatorData)); + + inventoryScrambler.Scramble(expected); + db.updateInventoryItem(expected); + + InventoryItemBase actual = db.getInventoryItem(rootId); + Assert.That(actual, Constraints.PropertyCompareConstraint(expected) + .IgnoreProperty(x => x.InvType) + .IgnoreProperty(x => x.CreatorIdAsUuid) + .IgnoreProperty(x => x.Description) + .IgnoreProperty(x => x.CreatorIdentification) + .IgnoreProperty(x => x.CreatorData)); + } + + [Test] + public void T999_StillNull() + { + TestHelpers.InMethod(); + + // After all tests are run, these should still return no results + Assert.That(db.getInventoryFolder(zero), Is.Null); + Assert.That(db.getInventoryItem(zero), Is.Null); + Assert.That(db.getUserRootFolder(zero), Is.Null); + Assert.That(db.getInventoryInFolder(zero).Count, Is.EqualTo(0), "Assert.That(db.getInventoryInFolder(zero).Count, Is.EqualTo(0))"); + } + + private InventoryItemBase NewItem(UUID id, UUID parent, UUID owner, string name, UUID asset) + { + InventoryItemBase i = new InventoryItemBase(); + i.ID = id; + i.Folder = parent; + i.Owner = owner; + i.CreatorId = owner.ToString(); + i.Name = name; + i.Description = name; + i.AssetID = asset; + return i; + } + + private InventoryFolderBase NewFolder(UUID id, UUID parent, UUID owner, string name) + { + InventoryFolderBase f = new InventoryFolderBase(); + f.ID = id; + f.ParentID = parent; + f.Owner = owner; + f.Name = name; + return f; + } + } +} diff --git a/Tests/OpenSim.Data.Tests/OpenSim.Data.Tests.csproj b/Tests/OpenSim.Data.Tests/OpenSim.Data.Tests.csproj new file mode 100644 index 00000000000..d52cbf35061 --- /dev/null +++ b/Tests/OpenSim.Data.Tests/OpenSim.Data.Tests.csproj @@ -0,0 +1,50 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Data.Tests/PropertyCompareConstraint.cs b/Tests/OpenSim.Data.Tests/PropertyCompareConstraint.cs new file mode 100644 index 00000000000..b99525a74a3 --- /dev/null +++ b/Tests/OpenSim.Data.Tests/PropertyCompareConstraint.cs @@ -0,0 +1,413 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Linq; +using System.Linq.Expressions; +using System.Reflection; +using NUnit.Framework; +using NUnit.Framework.Constraints; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Data.Tests +{ + public static class Constraints + { + //This is here because C# has a gap in the language, you can't infer type from a constructor + public static PropertyCompareConstraint PropertyCompareConstraint(T expected) + { + return new PropertyCompareConstraint(expected); + } + } + + public class PropertyCompareConstraint : NUnit.Framework.Constraints.Constraint + { + private readonly object _expected; + //the reason everywhere uses propertyNames.Reverse().ToArray() is because the stack is backwards of the order we want to display the properties in. + private string failingPropertyName = string.Empty; + private object failingExpected; + private object failingActual; + + public PropertyCompareConstraint(T expected) + { + _expected = expected; + } + + public override bool Matches(object actual) + { + return ObjectCompare(_expected, actual, new Stack()); + } + + private bool ObjectCompare(object expected, object actual, Stack propertyNames) + { + //If they are both null, they are equal + if (actual == null && expected == null) + return true; + + //If only one is null, then they aren't + if (actual == null || expected == null) + { + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + failingActual = actual; + failingExpected = expected; + return false; + } + + //prevent loops... + if (propertyNames.Count > 50) + { + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + failingActual = actual; + failingExpected = expected; + return false; + } + + if (actual.GetType() != expected.GetType()) + { + propertyNames.Push("GetType()"); + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + propertyNames.Pop(); + failingActual = actual.GetType(); + failingExpected = expected.GetType(); + return false; + } + + if (actual.GetType() == typeof(Color)) + { + Color actualColor = (Color) actual; + Color expectedColor = (Color) expected; + if (actualColor.R != expectedColor.R) + { + propertyNames.Push("R"); + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + propertyNames.Pop(); + failingActual = actualColor.R; + failingExpected = expectedColor.R; + return false; + } + if (actualColor.G != expectedColor.G) + { + propertyNames.Push("G"); + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + propertyNames.Pop(); + failingActual = actualColor.G; + failingExpected = expectedColor.G; + return false; + } + if (actualColor.B != expectedColor.B) + { + propertyNames.Push("B"); + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + propertyNames.Pop(); + failingActual = actualColor.B; + failingExpected = expectedColor.B; + return false; + } + if (actualColor.A != expectedColor.A) + { + propertyNames.Push("A"); + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + propertyNames.Pop(); + failingActual = actualColor.A; + failingExpected = expectedColor.A; + return false; + } + return true; + } + + IComparable comp = actual as IComparable; + if (comp != null) + { + if (comp.CompareTo(expected) != 0) + { + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + failingActual = actual; + failingExpected = expected; + return false; + } + return true; + } + + //Now try the much more annoying IComparable + Type icomparableInterface = actual.GetType().GetInterface("IComparable`1"); + if (icomparableInterface != null) + { + int result = (int)icomparableInterface.GetMethod("CompareTo").Invoke(actual, new[] { expected }); + if (result != 0) + { + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + failingActual = actual; + failingExpected = expected; + return false; + } + return true; + } + + IEnumerable arr = actual as IEnumerable; + if (arr != null) + { + List actualList = arr.Cast().ToList(); + List expectedList = ((IEnumerable)expected).Cast().ToList(); + if (actualList.Count != expectedList.Count) + { + propertyNames.Push("Count"); + failingPropertyName = string.Join(".", propertyNames.Reverse().ToArray()); + failingActual = actualList.Count; + failingExpected = expectedList.Count; + propertyNames.Pop(); + return false; + } + //actualList and expectedList should be the same size. + for (int i = 0; i < actualList.Count; i++) + { + propertyNames.Push("[" + i + "]"); + if (!ObjectCompare(expectedList[i], actualList[i], propertyNames)) + return false; + propertyNames.Pop(); + } + //Everything seems okay... + return true; + } + + //Skip static properties. I had a nasty problem comparing colors because of all of the public static colors. + PropertyInfo[] properties = expected.GetType().GetProperties(BindingFlags.Public | BindingFlags.Instance); + foreach (var property in properties) + { + if (ignores.Contains(property.Name)) + continue; + + object actualValue = property.GetValue(actual, null); + object expectedValue = property.GetValue(expected, null); + + propertyNames.Push(property.Name); + if (!ObjectCompare(expectedValue, actualValue, propertyNames)) + return false; + propertyNames.Pop(); + } + + return true; + } + + public override void WriteDescriptionTo(MessageWriter writer) + { + writer.WriteExpectedValue(failingExpected); + } + + public override void WriteActualValueTo(MessageWriter writer) + { + writer.WriteActualValue(failingActual); + writer.WriteLine(); + writer.Write(" On Property: " + failingPropertyName); + } + + //These notes assume the lambda: (x=>x.Parent.Value) + //ignores should really contain like a fully dotted version of the property name, but I'm starting with small steps + readonly List ignores = new List(); + public PropertyCompareConstraint IgnoreProperty(Expression> func) + { + Expression express = func.Body; + PullApartExpression(express); + + return this; + } + + private void PullApartExpression(Expression express) + { + //This deals with any casts... like implicit casts to object. Not all UnaryExpression are casts, but this is a first attempt. + if (express is UnaryExpression) + PullApartExpression(((UnaryExpression)express).Operand); + if (express is MemberExpression) + { + //If the inside of the lambda is the access to x, we've hit the end of the chain. + // We should track by the fully scoped parameter name, but this is the first rev of doing this. + ignores.Add(((MemberExpression)express).Member.Name); + } + } + } + + [TestFixture] + public class PropertyCompareConstraintTest : OpenSimTestCase + { + public class HasInt + { + public int TheValue { get; set; } + } + + [Test] + public void IntShouldMatch() + { + HasInt actual = new HasInt { TheValue = 5 }; + HasInt expected = new HasInt { TheValue = 5 }; + var constraint = Constraints.PropertyCompareConstraint(expected); + + Assert.That(constraint.Matches(actual), Is.True); + } + + [Test] + public void IntShouldNotMatch() + { + HasInt actual = new HasInt { TheValue = 5 }; + HasInt expected = new HasInt { TheValue = 4 }; + var constraint = Constraints.PropertyCompareConstraint(expected); + + Assert.That(constraint.Matches(actual), Is.False); + } + + + [Test] + public void IntShouldIgnore() + { + HasInt actual = new HasInt { TheValue = 5 }; + HasInt expected = new HasInt { TheValue = 4 }; + var constraint = Constraints.PropertyCompareConstraint(expected).IgnoreProperty(x => x.TheValue); + + Assert.That(constraint.Matches(actual), Is.True); + } + + [Test] + public void AssetShouldMatch() + { + UUID uuid1 = UUID.Random(); + AssetBase actual = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + AssetBase expected = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + + var constraint = Constraints.PropertyCompareConstraint(expected); + + Assert.That(constraint.Matches(actual), Is.True); + } + + [Test] + public void AssetShouldNotMatch() + { + UUID uuid1 = UUID.Random(); + AssetBase actual = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + AssetBase expected = new AssetBase(UUID.Random(), "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + + var constraint = Constraints.PropertyCompareConstraint(expected); + + Assert.That(constraint.Matches(actual), Is.False); + } + + [Test] + public void AssetShouldNotMatch2() + { + UUID uuid1 = UUID.Random(); + AssetBase actual = new AssetBase(uuid1, "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + AssetBase expected = new AssetBase(uuid1, "asset two", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + + var constraint = Constraints.PropertyCompareConstraint(expected); + + Assert.That(constraint.Matches(actual), Is.False); + } + + [Test] + public void UUIDShouldMatch() + { + UUID uuid1 = UUID.Random(); + UUID uuid2 = UUID.Parse(uuid1.ToString()); + + var constraint = Constraints.PropertyCompareConstraint(uuid1); + + Assert.That(constraint.Matches(uuid2), Is.True); + } + + [Test] + public void UUIDShouldNotMatch() + { + UUID uuid1 = UUID.Random(); + UUID uuid2 = UUID.Random(); + + var constraint = Constraints.PropertyCompareConstraint(uuid1); + + Assert.That(constraint.Matches(uuid2), Is.False); + } + + [Test] + public void TestColors() + { + Color actual = Color.Red; + Color expected = Color.FromArgb(actual.A, actual.R, actual.G, actual.B); + + var constraint = Constraints.PropertyCompareConstraint(expected); + + Assert.That(constraint.Matches(actual), Is.True); + } + + [Test] + public void ShouldCompareLists() + { + List expected = new List { 1, 2, 3 }; + List actual = new List { 1, 2, 3 }; + + var constraint = Constraints.PropertyCompareConstraint(expected); + Assert.That(constraint.Matches(actual), Is.True); + } + + + [Test] + public void ShouldFailToCompareListsThatAreDifferent() + { + List expected = new List { 1, 2, 3 }; + List actual = new List { 1, 2, 4 }; + + var constraint = Constraints.PropertyCompareConstraint(expected); + Assert.That(constraint.Matches(actual), Is.False); + } + + [Test] + public void ShouldFailToCompareListsThatAreDifferentLengths() + { + List expected = new List { 1, 2, 3 }; + List actual = new List { 1, 2 }; + + var constraint = Constraints.PropertyCompareConstraint(expected); + Assert.That(constraint.Matches(actual), Is.False); + } + + public class Recursive + { + public Recursive Other { get; set; } + } + + [Test] + public void ErrorsOutOnRecursive() + { + Recursive parent = new Recursive(); + Recursive child = new Recursive(); + parent.Other = child; + child.Other = parent; + + var constraint = Constraints.PropertyCompareConstraint(child); + Assert.That(constraint.Matches(child), Is.False); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Data.Tests/PropertyScrambler.cs b/Tests/OpenSim.Data.Tests/PropertyScrambler.cs new file mode 100644 index 00000000000..0d291df0b32 --- /dev/null +++ b/Tests/OpenSim.Data.Tests/PropertyScrambler.cs @@ -0,0 +1,184 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq.Expressions; +using System.Reflection; +using System.Text; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Data.Tests +{ + //This is generic so that the lambda expressions will work right in IDEs. + public class PropertyScrambler + { + readonly System.Collections.Generic.List membersToNotScramble = new List(); + + private void AddExpressionToNotScrableList(Expression expression) + { + UnaryExpression unaryExpression = expression as UnaryExpression; + if (unaryExpression != null) + { + AddExpressionToNotScrableList(unaryExpression.Operand); + return; + } + + MemberExpression memberExpression = expression as MemberExpression; + if (memberExpression != null) + { + if (!(memberExpression.Member is PropertyInfo)) + { + throw new NotImplementedException("I don't know how deal with a MemberExpression that is a " + expression.Type); + } + membersToNotScramble.Add(memberExpression.Member.Name); + return; + } + + throw new NotImplementedException("I don't know how to parse a " + expression.Type); + } + + public PropertyScrambler DontScramble(Expression> expression) + { + AddExpressionToNotScrableList(expression.Body); + return this; + } + + public void Scramble(T obj) + { + internalScramble(obj); + } + + private void internalScramble(object obj) + { + PropertyInfo[] properties = obj.GetType().GetProperties(); + foreach (var property in properties) + { + //Skip indexers of classes. We will assume that everything that has an indexer + // is also IEnumberable. May not always be true, but should be true normally. + if (property.GetIndexParameters().Length > 0) + continue; + + RandomizeProperty(obj, property, null); + } + //Now if it implments IEnumberable, it's probably some kind of list, so we should randomize + // everything inside of it. + IEnumerable enumerable = obj as IEnumerable; + if (enumerable != null) + { + foreach (object value in enumerable) + { + internalScramble(value); + } + } + } + + private readonly Random random = new Random(); + private void RandomizeProperty(object obj, PropertyInfo property, object[] index) + {//I'd like a better way to compare, but I had lots of problems with InventoryFolderBase because the ID is inherited. + if (membersToNotScramble.Contains(property.Name)) + return; + Type t = property.PropertyType; + if (!property.CanWrite) + return; + object value = property.GetValue(obj, index); + if (value == null) + return; + + if (t == typeof(string)) + property.SetValue(obj, RandomName(), index); + else if (t == typeof(UUID)) + property.SetValue(obj, UUID.Random(), index); + else if (t == typeof(sbyte)) + property.SetValue(obj, (sbyte)random.Next(sbyte.MinValue, sbyte.MaxValue), index); + else if (t == typeof(short)) + property.SetValue(obj, (short)random.Next(short.MinValue, short.MaxValue), index); + else if (t == typeof(int)) + property.SetValue(obj, random.Next(), index); + else if (t == typeof(long)) + property.SetValue(obj, random.Next() * int.MaxValue, index); + else if (t == typeof(byte)) + property.SetValue(obj, (byte)random.Next(byte.MinValue, byte.MaxValue), index); + else if (t == typeof(ushort)) + property.SetValue(obj, (ushort)random.Next(ushort.MinValue, ushort.MaxValue), index); + else if (t == typeof(uint)) + property.SetValue(obj, Convert.ToUInt32(random.Next()), index); + else if (t == typeof(ulong)) + property.SetValue(obj, Convert.ToUInt64(random.Next()) * Convert.ToUInt64(UInt32.MaxValue), index); + else if (t == typeof(bool)) + property.SetValue(obj, true, index); + else if (t == typeof(byte[])) + { + byte[] bytes = new byte[30]; + random.NextBytes(bytes); + property.SetValue(obj, bytes, index); + } + else + internalScramble(value); + } + + private string RandomName() + { + StringBuilder name = new StringBuilder(); + int size = random.Next(5, 12); + for (int i = 0; i < size; i++) + { + char ch = Convert.ToChar(Convert.ToInt32(Math.Floor(26 * random.NextDouble() + 65))); + name.Append(ch); + } + return name.ToString(); + } + } + + [TestFixture] + public class PropertyScramblerTests : OpenSimTestCase + { + [Test] + public void TestScramble() + { + AssetBase actual = new AssetBase(UUID.Random(), "asset one", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + new PropertyScrambler().Scramble(actual); + } + + [Test] + public void DontScramble() + { + UUID uuid = UUID.Random(); + AssetBase asset = new AssetBase(uuid, "asset", (sbyte)AssetType.Texture, UUID.Zero.ToString()); + new PropertyScrambler() + .DontScramble(x => x.Metadata) + .DontScramble(x => x.FullID) + .DontScramble(x => x.ID) + .Scramble(asset); + Assert.That(asset.FullID, Is.EqualTo(uuid)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Data.Tests/RegionTests.cs b/Tests/OpenSim.Data.Tests/RegionTests.cs new file mode 100644 index 00000000000..75d644aa0af --- /dev/null +++ b/Tests/OpenSim.Data.Tests/RegionTests.cs @@ -0,0 +1,1145 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; +using System.Data.Common; +using System.Threading; + +// DBMS-specific: +using OpenSim.Data.MySQL; + +using System.Data.SQLite; +using OpenSim.Data.SQLite; +using MySqlConnector; + +namespace OpenSim.Data.Tests +{ + [TestFixture(Description = "Region store tests (SQLite)")] + public class SQLiteRegionTests : RegionTests + { + } + + [TestFixture(Description = "Region store tests (MySQL)")] + public class MySqlRegionTests : RegionTests + { + } + + public class RegionTests : BasicDataServiceTest + where TConn : DbConnection, new() + where TRegStore : class, ISimulationDataStore, new() + { + bool m_rebuildDB; + + public ISimulationDataStore db; + public UUID zero = UUID.Zero; + public UUID region1 = UUID.Random(); + public UUID region2 = UUID.Random(); + public UUID region3 = UUID.Random(); + public UUID region4 = UUID.Random(); + public UUID prim1 = UUID.Random(); + public UUID prim2 = UUID.Random(); + public UUID prim3 = UUID.Random(); + public UUID prim4 = UUID.Random(); + public UUID prim5 = UUID.Random(); + public UUID prim6 = UUID.Random(); + public UUID item1 = UUID.Random(); + public UUID item2 = UUID.Random(); + public UUID item3 = UUID.Random(); + + public static Random random = new Random(); + + public string itemname1 = "item1"; + + public uint localID = 1; + + public double height1 = 20; + public double height2 = 100; + + public RegionTests(string conn, bool rebuild) + : base(conn) + { + m_rebuildDB = rebuild; + } + + public RegionTests() : this("", true) { } + public RegionTests(string conn) : this(conn, true) {} + public RegionTests(bool rebuild): this("", rebuild) {} + + + protected override void InitService(object service) + { + ClearDB(); + db = (ISimulationDataStore)service; + db.Initialise(m_connStr); + } + + private void ClearDB() + { + string[] reg_tables = new string[] { + "prims", "primshapes", "primitems", "terrain", "land", "landaccesslist", "regionban", "regionsettings" + }; + + if (m_rebuildDB) + { + DropTables(reg_tables); + ResetMigrations("RegionStore"); + } + else + { + ClearTables(reg_tables); + } + } + + // Test Plan + // Prims + // - empty test - 001 + // - store / retrieve basic prims (most minimal we can make) - 010, 011 + // - store / retrieve parts in a scenegroup 012 + // - store a prim with complete information for consistency check 013 + // - update existing prims, make sure it sticks - 014 + // - tests empty inventory - 020 + // - add inventory items to prims make - 021 + // - retrieves the added item - 022 + // - update inventory items to prims - 023 + // - remove inventory items make sure it sticks - 024 + // - checks if all parameters are persistent - 025 + // - adds many items and see if it is handled correctly - 026 + + [Test] + public void T001_LoadEmpty() + { + TestHelpers.InMethod(); + + List objs = db.LoadObjects(region1); + List objs3 = db.LoadObjects(region3); + List land = db.LoadLandObjects(region1); + + Assert.That(objs.Count, Is.EqualTo(0), "Assert.That(objs.Count, Is.EqualTo(0))"); + Assert.That(objs3.Count, Is.EqualTo(0), "Assert.That(objs3.Count, Is.EqualTo(0))"); + Assert.That(land.Count, Is.EqualTo(0), "Assert.That(land.Count, Is.EqualTo(0))"); + } + + // SOG round trips + // * store objects, make sure they save + // * update + + [Test] + public void T010_StoreSimpleObject() + { + TestHelpers.InMethod(); + + SceneObjectGroup sog = NewSOG("object1", prim1, region1); + SceneObjectGroup sog2 = NewSOG("object2", prim2, region1); + + // in case the objects don't store + try + { + db.StoreObject(sog, region1); + } + catch (Exception e) + { + m_log.Error(e.ToString()); + Assert.Fail(); + } + + try + { + db.StoreObject(sog2, region1); + } + catch (Exception e) + { + m_log.Error(e.ToString()); + Assert.Fail(); + } + + // This tests the ADO.NET driver + List objs = db.LoadObjects(region1); + + Assert.That(objs.Count, Is.EqualTo(2), "Assert.That(objs.Count, Is.EqualTo(2))"); + } + + [Test] + public void T011_ObjectNames() + { + TestHelpers.InMethod(); + + List objs = db.LoadObjects(region1); + foreach (SceneObjectGroup sog in objs) + { + SceneObjectPart p = sog.RootPart; + Assert.That("", Is.Not.EqualTo(p.Name), "Assert.That(\"\", Is.Not.EqualTo(p.Name))"); + Assert.That(p.Name, Is.EqualTo(p.Description), "Assert.That(p.Name, Is.EqualTo(p.Description))"); + } + } + + [Test] + public void T012_SceneParts() + { + TestHelpers.InMethod(); + + UUID tmp0 = UUID.Random(); + UUID tmp1 = UUID.Random(); + UUID tmp2 = UUID.Random(); + UUID tmp3 = UUID.Random(); + UUID newregion = UUID.Random(); + SceneObjectPart p1 = NewSOP("SoP 1",tmp1); + SceneObjectPart p2 = NewSOP("SoP 2",tmp2); + SceneObjectPart p3 = NewSOP("SoP 3",tmp3); + SceneObjectGroup sog = NewSOG("Sop 0", tmp0, newregion); + sog.AddPart(p1); + sog.AddPart(p2); + sog.AddPart(p3); + + SceneObjectPart[] parts = sog.Parts; + Assert.That(parts.Length,Is.EqualTo(4), "Assert.That(parts.Length,Is.EqualTo(4))"); + + db.StoreObject(sog, newregion); + List sogs = db.LoadObjects(newregion); + Assert.That(sogs.Count,Is.EqualTo(1), "Assert.That(sogs.Count,Is.EqualTo(1))"); + SceneObjectGroup newsog = sogs[0]; + + SceneObjectPart[] newparts = newsog.Parts; + Assert.That(newparts.Length,Is.EqualTo(4), "Assert.That(newparts.Length,Is.EqualTo(4))"); + + Assert.That(newsog.ContainsPart(tmp0), "Assert.That(newsog.ContainsPart(tmp0))"); + Assert.That(newsog.ContainsPart(tmp1), "Assert.That(newsog.ContainsPart(tmp1))"); + Assert.That(newsog.ContainsPart(tmp2), "Assert.That(newsog.ContainsPart(tmp2))"); + Assert.That(newsog.ContainsPart(tmp3), "Assert.That(newsog.ContainsPart(tmp3))"); + } + + [Test] + public void T013_DatabasePersistency() + { + TestHelpers.InMethod(); + + // Sets all ScenePart parameters, stores and retrieves them, then check for consistency with initial data + // The commented Asserts are the ones that are unchangeable (when storing on the database, their "Set" values are ignored + // The ObjectFlags is an exception, if it is entered incorrectly, the object IS REJECTED on the database silently. + UUID creator,uuid = new UUID(); + creator = UUID.Random(); + uint iserial = (uint)random.Next(); + TaskInventoryDictionary dic = new TaskInventoryDictionary(); + uint objf = (uint) random.Next() & (uint)~(PrimFlags.Touch | PrimFlags.Money | PrimFlags.AllowInventoryDrop); + uuid = prim4; + uint localid = localID+1; + localID = localID + 1; + string name = "Adam West"; + byte material = (byte) random.Next((int)SOPMaterialData.MaxMaterial); + ulong regionh = (ulong)random.NextDouble() * (ulong)random.Next(); + int pin = random.Next(); + Byte[] partsys = new byte[8]; + Byte[] textani = new byte[8]; + random.NextBytes(textani); + random.NextBytes(partsys); + DateTime expires = new DateTime(2008, 12, 20); + DateTime rezzed = new DateTime(2009, 07, 15); + Vector3 groupos = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 offset = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Quaternion rotoff = new Quaternion(random.Next(1),random.Next(1),random.Next(1),random.Next(1)); + rotoff.Normalize(); + Vector3 velocity = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 angvelo = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 accel = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + string description = name; + Color color = Color.FromArgb(255, 165, 50, 100); + string text = "All Your Base Are Belong to Us"; + string sitname = "SitName"; + string touchname = "TouchName"; + int linknum = random.Next(); + byte clickaction = (byte) random.Next(127); + PrimitiveBaseShape pbshap = new PrimitiveBaseShape(); + pbshap = PrimitiveBaseShape.Default; + pbshap.PathBegin = ushort.MaxValue; + pbshap.PathEnd = ushort.MaxValue; + pbshap.ProfileBegin = ushort.MaxValue; + pbshap.ProfileEnd = ushort.MaxValue; + pbshap.ProfileHollow = ushort.MaxValue; + Vector3 scale = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + + RegionInfo regionInfo = new RegionInfo() + { + RegionID = region3, + RegionLocX = 0, + RegionLocY = 0 + }; + + SceneObjectPart sop = new SceneObjectPart(); + SceneObjectGroup sog = new SceneObjectGroup(sop); + + sop.RegionHandle = regionh; + sop.UUID = uuid; + sop.LocalId = localid; + sop.Shape = pbshap; + sop.GroupPosition = groupos; + sop.RotationOffset = rotoff; + sop.CreatorID = creator; + sop.InventorySerial = iserial; + sop.TaskInventory = dic; + sop.Flags = (PrimFlags)objf; + sop.Name = name; + sop.Material = material; + sop.ScriptAccessPin = pin; + sop.TextureAnimation = textani; + sop.ParticleSystem = partsys; + sop.Expires = expires; + sop.Rezzed = rezzed; + sop.OffsetPosition = offset; + sop.Velocity = velocity; + sop.AngularVelocity = angvelo; + sop.Acceleration = accel; + sop.Description = description; + sop.Color = color; + sop.Text = text; + sop.SitName = sitname; + sop.TouchName = touchname; + sop.LinkNum = linknum; + sop.ClickAction = clickaction; + sop.Scale = scale; + + //Tests if local part accepted the parameters: + Assert.That(regionh,Is.EqualTo(sop.RegionHandle), "Assert.That(regionh,Is.EqualTo(sop.RegionHandle))"); + Assert.That(localid,Is.EqualTo(sop.LocalId), "Assert.That(localid,Is.EqualTo(sop.LocalId))"); + Assert.That(groupos,Is.EqualTo(sop.GroupPosition), "Assert.That(groupos,Is.EqualTo(sop.GroupPosition))"); + Assert.That(name,Is.EqualTo(sop.Name), "Assert.That(name,Is.EqualTo(sop.Name))"); + Assert.That(rotoff, new QuaternionToleranceConstraint(sop.RotationOffset, 0.001), "Assert.That(rotoff,Is.EqualTo(sop.RotationOffset))"); + Assert.That(uuid,Is.EqualTo(sop.UUID), "Assert.That(uuid,Is.EqualTo(sop.UUID))"); + Assert.That(creator,Is.EqualTo(sop.CreatorID), "Assert.That(creator,Is.EqualTo(sop.CreatorID))"); + // Modified in-class + // Assert.That(iserial,Is.EqualTo(sop.InventorySerial), "Assert.That(iserial,Is.EqualTo(sop.InventorySerial))"); + Assert.That(dic,Is.EqualTo(sop.TaskInventory), "Assert.That(dic,Is.EqualTo(sop.TaskInventory))"); + Assert.That(objf, Is.EqualTo((uint)sop.Flags), "Assert.That(objf,Is.EqualTo(sop.Flags))"); + Assert.That(name,Is.EqualTo(sop.Name), "Assert.That(name,Is.EqualTo(sop.Name))"); + Assert.That(material,Is.EqualTo(sop.Material), "Assert.That(material,Is.EqualTo(sop.Material))"); + Assert.That(pin,Is.EqualTo(sop.ScriptAccessPin), "Assert.That(pin,Is.EqualTo(sop.ScriptAccessPin))"); + Assert.That(textani,Is.EqualTo(sop.TextureAnimation), "Assert.That(textani,Is.EqualTo(sop.TextureAnimation))"); + Assert.That(partsys,Is.EqualTo(sop.ParticleSystem), "Assert.That(partsys,Is.EqualTo(sop.ParticleSystem))"); + Assert.That(expires,Is.EqualTo(sop.Expires), "Assert.That(expires,Is.EqualTo(sop.Expires))"); + Assert.That(rezzed,Is.EqualTo(sop.Rezzed), "Assert.That(rezzed,Is.EqualTo(sop.Rezzed))"); + Assert.That(offset,Is.EqualTo(sop.OffsetPosition), "Assert.That(offset,Is.EqualTo(sop.OffsetPosition))"); + Assert.That(velocity,Is.EqualTo(sop.Velocity), "Assert.That(velocity,Is.EqualTo(sop.Velocity))"); + Assert.That(angvelo,Is.EqualTo(sop.AngularVelocity), "Assert.That(angvelo,Is.EqualTo(sop.AngularVelocity))"); + Assert.That(accel,Is.EqualTo(sop.Acceleration), "Assert.That(accel,Is.EqualTo(sop.Acceleration))"); + Assert.That(description,Is.EqualTo(sop.Description), "Assert.That(description,Is.EqualTo(sop.Description))"); + Assert.That(color,Is.EqualTo(sop.Color), "Assert.That(color,Is.EqualTo(sop.Color))"); + Assert.That(text,Is.EqualTo(sop.Text), "Assert.That(text,Is.EqualTo(sop.Text))"); + Assert.That(sitname,Is.EqualTo(sop.SitName), "Assert.That(sitname,Is.EqualTo(sop.SitName))"); + Assert.That(touchname,Is.EqualTo(sop.TouchName), "Assert.That(touchname,Is.EqualTo(sop.TouchName))"); + Assert.That(linknum,Is.EqualTo(sop.LinkNum), "Assert.That(linknum,Is.EqualTo(sop.LinkNum))"); + Assert.That(clickaction,Is.EqualTo(sop.ClickAction), "Assert.That(clickaction,Is.EqualTo(sop.ClickAction))"); + Assert.That(scale,Is.EqualTo(sop.Scale), "Assert.That(scale,Is.EqualTo(sop.Scale))"); + + // This is necessary or object will not be inserted in DB + sop.Flags = PrimFlags.None; + + // Inserts group in DB + db.StoreObject(sog,region3); + List sogs = db.LoadObjects(region3); + Assert.That(sogs.Count, Is.EqualTo(1), "Assert.That(sogs.Count, Is.EqualTo(1))"); + // Makes sure there are no double insertions: + db.StoreObject(sog,region3); + sogs = db.LoadObjects(region3); + Assert.That(sogs.Count, Is.EqualTo(1), "Assert.That(sogs.Count, Is.EqualTo(1))"); + + + // Tests if the parameters were inserted correctly + SceneObjectPart p = sogs[0].RootPart; + Assert.That(regionh,Is.EqualTo(p.RegionHandle), "Assert.That(regionh,Is.EqualTo(p.RegionHandle))"); + //Assert.That(localid,Is.EqualTo(p.LocalId), "Assert.That(localid,Is.EqualTo(p.LocalId))"); + Assert.That(groupos, Is.EqualTo(p.GroupPosition), "Assert.That(groupos,Is.EqualTo(p.GroupPosition))"); + Assert.That(name,Is.EqualTo(p.Name), "Assert.That(name,Is.EqualTo(p.Name))"); + Assert.That(rotoff, Is.EqualTo(p.RotationOffset), "Assert.That(rotoff,Is.EqualTo(p.RotationOffset))"); + Assert.That(uuid,Is.EqualTo(p.UUID), "Assert.That(uuid,Is.EqualTo(p.UUID))"); + Assert.That(creator,Is.EqualTo(p.CreatorID), "Assert.That(creator,Is.EqualTo(p.CreatorID))"); + //Assert.That(iserial,Is.EqualTo(p.InventorySerial), "Assert.That(iserial,Is.EqualTo(p.InventorySerial))"); + Assert.That(dic,Is.EqualTo(p.TaskInventory), "Assert.That(dic,Is.EqualTo(p.TaskInventory))"); + //Assert.That(objf, Is.EqualTo((uint)p.Flags), "Assert.That(objf,Is.EqualTo(p.Flags))"); + Assert.That(name,Is.EqualTo(p.Name), "Assert.That(name,Is.EqualTo(p.Name))"); + Assert.That(material,Is.EqualTo(p.Material), "Assert.That(material,Is.EqualTo(p.Material))"); + Assert.That(pin,Is.EqualTo(p.ScriptAccessPin), "Assert.That(pin,Is.EqualTo(p.ScriptAccessPin))"); + Assert.That(textani,Is.EqualTo(p.TextureAnimation), "Assert.That(textani,Is.EqualTo(p.TextureAnimation))"); + Assert.That(partsys,Is.EqualTo(p.ParticleSystem), "Assert.That(partsys,Is.EqualTo(p.ParticleSystem))"); + //Assert.That(expires,Is.EqualTo(p.Expires), "Assert.That(expires,Is.EqualTo(p.Expires))"); + //Assert.That(rezzed,Is.EqualTo(p.Rezzed), "Assert.That(rezzed,Is.EqualTo(p.Rezzed))"); + Assert.That(offset,Is.EqualTo(p.OffsetPosition), "Assert.That(offset,Is.EqualTo(p.OffsetPosition))"); + Assert.That(velocity,Is.EqualTo(p.Velocity), "Assert.That(velocity,Is.EqualTo(p.Velocity))"); + Assert.That(angvelo,Is.EqualTo(p.AngularVelocity), "Assert.That(angvelo,Is.EqualTo(p.AngularVelocity))"); + Assert.That(accel,Is.EqualTo(p.Acceleration), "Assert.That(accel,Is.EqualTo(p.Acceleration))"); + Assert.That(description,Is.EqualTo(p.Description), "Assert.That(description,Is.EqualTo(p.Description))"); + Assert.That(color,Is.EqualTo(p.Color), "Assert.That(color,Is.EqualTo(p.Color))"); + Assert.That(text,Is.EqualTo(p.Text), "Assert.That(text,Is.EqualTo(p.Text))"); + Assert.That(sitname,Is.EqualTo(p.SitName), "Assert.That(sitname,Is.EqualTo(p.SitName))"); + Assert.That(touchname,Is.EqualTo(p.TouchName), "Assert.That(touchname,Is.EqualTo(p.TouchName))"); + //Assert.That(linknum,Is.EqualTo(p.LinkNum), "Assert.That(linknum,Is.EqualTo(p.LinkNum))"); + Assert.That(clickaction,Is.EqualTo(p.ClickAction), "Assert.That(clickaction,Is.EqualTo(p.ClickAction))"); + Assert.That(scale,Is.EqualTo(p.Scale), "Assert.That(scale,Is.EqualTo(p.Scale))"); + + //Assert.That(updatef,Is.EqualTo(p.UpdateFlag), "Assert.That(updatef,Is.EqualTo(p.UpdateFlag))"); + + Assert.That(pbshap.PathBegin, Is.EqualTo(p.Shape.PathBegin), "Assert.That(pbshap.PathBegin, Is.EqualTo(p.Shape.PathBegin))"); + Assert.That(pbshap.PathEnd, Is.EqualTo(p.Shape.PathEnd), "Assert.That(pbshap.PathEnd, Is.EqualTo(p.Shape.PathEnd))"); + Assert.That(pbshap.ProfileBegin, Is.EqualTo(p.Shape.ProfileBegin), "Assert.That(pbshap.ProfileBegin, Is.EqualTo(p.Shape.ProfileBegin))"); + Assert.That(pbshap.ProfileEnd, Is.EqualTo(p.Shape.ProfileEnd), "Assert.That(pbshap.ProfileEnd, Is.EqualTo(p.Shape.ProfileEnd))"); + Assert.That(pbshap.ProfileHollow, Is.EqualTo(p.Shape.ProfileHollow), "Assert.That(pbshap.ProfileHollow, Is.EqualTo(p.Shape.ProfileHollow))"); + } + + [Test] + public void T014_UpdateObject() + { + TestHelpers.InMethod(); + + string text1 = "object1 text"; + SceneObjectGroup sog = FindSOG("object1", region1); + sog.RootPart.Text = text1; + db.StoreObject(sog, region1); + + sog = FindSOG("object1", region1); + Assert.That(text1, Is.EqualTo(sog.RootPart.Text), "Assert.That(text1, Is.EqualTo(sog.RootPart.Text))"); + + // Creates random values + UUID creator = new UUID(); + creator = UUID.Random(); + TaskInventoryDictionary dic = new TaskInventoryDictionary(); + localID = localID + 1; + string name = "West Adam"; + byte material = (byte) random.Next((int)SOPMaterialData.MaxMaterial); + ulong regionh = (ulong)random.NextDouble() * (ulong)random.Next(); + int pin = random.Next(); + Byte[] partsys = new byte[8]; + Byte[] textani = new byte[8]; + random.NextBytes(textani); + random.NextBytes(partsys); + DateTime expires = new DateTime(2010, 12, 20); + DateTime rezzed = new DateTime(2005, 07, 15); + Vector3 groupos = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 offset = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Quaternion rotoff = new Quaternion(random.Next(100),random.Next(100),random.Next(100),random.Next(100)); + rotoff.Normalize(); + Vector3 velocity = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 angvelo = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 accel = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + string description = name; + Color color = Color.FromArgb(255, 255, 255, 0); + string text = "What You Say?{]\vz~"; + string sitname = RandomName(); + string touchname = RandomName(); + int linknum = random.Next(); + byte clickaction = (byte) random.Next(127); + PrimitiveBaseShape pbshap = new PrimitiveBaseShape(); + pbshap = PrimitiveBaseShape.Default; + Vector3 scale = new Vector3(random.Next(),random.Next(),random.Next()); + + // Updates the region with new values + SceneObjectGroup sog2 = FindSOG("Adam West", region3); + Assert.That(sog2,Is.Not.Null); + sog2.RootPart.RegionHandle = regionh; + sog2.RootPart.Shape = pbshap; + sog2.RootPart.GroupPosition = groupos; + sog2.RootPart.RotationOffset = rotoff; + sog2.RootPart.CreatorID = creator; + sog2.RootPart.TaskInventory = dic; + sog2.RootPart.Name = name; + sog2.RootPart.Material = material; + sog2.RootPart.ScriptAccessPin = pin; + sog2.RootPart.TextureAnimation = textani; + sog2.RootPart.ParticleSystem = partsys; + sog2.RootPart.Expires = expires; + sog2.RootPart.Rezzed = rezzed; + sog2.RootPart.OffsetPosition = offset; + sog2.RootPart.Velocity = velocity; + sog2.RootPart.AngularVelocity = angvelo; + sog2.RootPart.Acceleration = accel; + sog2.RootPart.Description = description; + sog2.RootPart.Color = color; + sog2.RootPart.Text = text; + sog2.RootPart.SitName = sitname; + sog2.RootPart.TouchName = touchname; + sog2.RootPart.LinkNum = linknum; + sog2.RootPart.ClickAction = clickaction; + sog2.RootPart.Scale = scale; + + db.StoreObject(sog2, region3); + List sogs = db.LoadObjects(region3); + Assert.That(sogs.Count, Is.EqualTo(1), "Assert.That(sogs.Count, Is.EqualTo(1))"); + + SceneObjectGroup retsog = FindSOG("West Adam", region3); + Assert.That(retsog,Is.Not.Null); + SceneObjectPart p = retsog.RootPart; + Assert.That(regionh,Is.EqualTo(p.RegionHandle), "Assert.That(regionh,Is.EqualTo(p.RegionHandle))"); + Assert.That(groupos,Is.EqualTo(p.GroupPosition), "Assert.That(groupos,Is.EqualTo(p.GroupPosition))"); + Assert.That(name,Is.EqualTo(p.Name), "Assert.That(name,Is.EqualTo(p.Name))"); + Assert.That(rotoff, new QuaternionToleranceConstraint(p.RotationOffset, 0.001), "Assert.That(rotoff,Is.EqualTo(p.RotationOffset))"); + Assert.That(creator,Is.EqualTo(p.CreatorID), "Assert.That(creator,Is.EqualTo(p.CreatorID))"); + Assert.That(dic,Is.EqualTo(p.TaskInventory), "Assert.That(dic,Is.EqualTo(p.TaskInventory))"); + Assert.That(name,Is.EqualTo(p.Name), "Assert.That(name,Is.EqualTo(p.Name))"); + Assert.That(material,Is.EqualTo(p.Material), "Assert.That(material,Is.EqualTo(p.Material))"); + Assert.That(pin,Is.EqualTo(p.ScriptAccessPin), "Assert.That(pin,Is.EqualTo(p.ScriptAccessPin))"); + Assert.That(textani,Is.EqualTo(p.TextureAnimation), "Assert.That(textani,Is.EqualTo(p.TextureAnimation))"); + Assert.That(partsys,Is.EqualTo(p.ParticleSystem), "Assert.That(partsys,Is.EqualTo(p.ParticleSystem))"); + Assert.That(offset,Is.EqualTo(p.OffsetPosition), "Assert.That(offset,Is.EqualTo(p.OffsetPosition))"); + Assert.That(velocity,Is.EqualTo(p.Velocity), "Assert.That(velocity,Is.EqualTo(p.Velocity))"); + Assert.That(angvelo,Is.EqualTo(p.AngularVelocity), "Assert.That(angvelo,Is.EqualTo(p.AngularVelocity))"); + Assert.That(accel,Is.EqualTo(p.Acceleration), "Assert.That(accel,Is.EqualTo(p.Acceleration))"); + Assert.That(description,Is.EqualTo(p.Description), "Assert.That(description,Is.EqualTo(p.Description))"); + Assert.That(color,Is.EqualTo(p.Color), "Assert.That(color,Is.EqualTo(p.Color))"); + Assert.That(text,Is.EqualTo(p.Text), "Assert.That(text,Is.EqualTo(p.Text))"); + Assert.That(sitname,Is.EqualTo(p.SitName), "Assert.That(sitname,Is.EqualTo(p.SitName))"); + Assert.That(touchname,Is.EqualTo(p.TouchName), "Assert.That(touchname,Is.EqualTo(p.TouchName))"); + Assert.That(clickaction,Is.EqualTo(p.ClickAction), "Assert.That(clickaction,Is.EqualTo(p.ClickAction))"); + Assert.That(scale,Is.EqualTo(p.Scale), "Assert.That(scale,Is.EqualTo(p.Scale))"); + } + + /// + /// Test storage and retrieval of a scene object with a large number of parts. + /// + [Test] + public void T015_LargeSceneObjects() + { + TestHelpers.InMethod(); + + UUID id = UUID.Random(); + Dictionary mydic = new Dictionary(); + SceneObjectGroup sog = NewSOG("Test SOG", id, region4); + mydic.Add(sog.RootPart.UUID,sog.RootPart); + for (int i = 0; i < 30; i++) + { + UUID tmp = UUID.Random(); + SceneObjectPart sop = NewSOP(("Test SOP " + i.ToString()),tmp); + Vector3 groupos = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 offset = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Quaternion rotoff = new Quaternion(random.Next(1000),random.Next(1000),random.Next(1000),random.Next(1000)); + rotoff.Normalize(); + Vector3 velocity = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 angvelo = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + Vector3 accel = new Vector3(random.Next(100000),random.Next(100000),random.Next(100000)); + + sop.GroupPosition = groupos; + sop.RotationOffset = rotoff; + sop.OffsetPosition = offset; + sop.Velocity = velocity; + sop.AngularVelocity = angvelo; + sop.Acceleration = accel; + + mydic.Add(tmp,sop); + sog.AddPart(sop); + } + + db.StoreObject(sog, region4); + SceneObjectGroup retsog = FindSOG("Test SOG", region4); + SceneObjectPart[] parts = retsog.Parts; + for (int i = 0; i < 30; i++) + { + SceneObjectPart cursop = mydic[parts[i].UUID]; + Assert.That(cursop.GroupPosition,Is.EqualTo(parts[i].GroupPosition), "Assert.That(cursop.GroupPosition,Is.EqualTo(parts[i].GroupPosition))"); + Assert.That(cursop.RotationOffset, new QuaternionToleranceConstraint(parts[i].RotationOffset, 0.001), "Assert.That(rotoff,Is.EqualTo(p.RotationOffset))"); + Assert.That(cursop.OffsetPosition,Is.EqualTo(parts[i].OffsetPosition), "Assert.That(cursop.OffsetPosition,Is.EqualTo(parts[i].OffsetPosition))"); + Assert.That(cursop.Velocity,Is.EqualTo(parts[i].Velocity), "Assert.That(cursop.Velocity,Is.EqualTo(parts[i].Velocity))"); + Assert.That(cursop.AngularVelocity,Is.EqualTo(parts[i].AngularVelocity), "Assert.That(cursop.AngularVelocity,Is.EqualTo(parts[i].AngularVelocity))"); + Assert.That(cursop.Acceleration,Is.EqualTo(parts[i].Acceleration), "Assert.That(cursop.Acceleration,Is.EqualTo(parts[i].Acceleration))"); + } + } + + //[Test] + public void T016_RandomSogWithSceneParts() + { + TestHelpers.InMethod(); + + PropertyScrambler scrambler = + new PropertyScrambler() + .DontScramble(x => x.UUID); + UUID tmpSog = UUID.Random(); + UUID tmp1 = UUID.Random(); + UUID tmp2 = UUID.Random(); + UUID tmp3 = UUID.Random(); + UUID newregion = UUID.Random(); + SceneObjectPart p1 = new SceneObjectPart(); + SceneObjectPart p2 = new SceneObjectPart(); + SceneObjectPart p3 = new SceneObjectPart(); + p1.Shape = PrimitiveBaseShape.Default; + p2.Shape = PrimitiveBaseShape.Default; + p3.Shape = PrimitiveBaseShape.Default; + p1.UUID = tmp1; + p2.UUID = tmp2; + p3.UUID = tmp3; + scrambler.Scramble(p1); + scrambler.Scramble(p2); + scrambler.Scramble(p3); + + SceneObjectGroup sog = NewSOG("Sop 0", tmpSog, newregion); + PropertyScrambler sogScrambler = + new PropertyScrambler() + .DontScramble(x => x.UUID); + sogScrambler.Scramble(sog); + sog.UUID = tmpSog; + sog.AddPart(p1); + sog.AddPart(p2); + sog.AddPart(p3); + + SceneObjectPart[] parts = sog.Parts; + Assert.That(parts.Length, Is.EqualTo(4), "Assert.That(parts.Length,Is.EqualTo(4))"); + + db.StoreObject(sog, newregion); + List sogs = db.LoadObjects(newregion); + Assert.That(sogs.Count, Is.EqualTo(1), "Assert.That(sogs.Count,Is.EqualTo(1))"); + SceneObjectGroup newsog = sogs[0]; + + SceneObjectPart[] newparts = newsog.Parts; + Assert.That(newparts.Length, Is.EqualTo(4), "Assert.That(newparts.Length,Is.EqualTo(4))"); + + Assert.That(newsog, Constraints.PropertyCompareConstraint(sog) + .IgnoreProperty(x=>x.LocalId) + .IgnoreProperty(x=>x.HasGroupChanged) + .IgnoreProperty(x=>x.IsSelected) + .IgnoreProperty(x=>x.RegionHandle) + .IgnoreProperty(x=>x.RegionUUID) + .IgnoreProperty(x=>x.Scene) + .IgnoreProperty(x=>x.Parts) + .IgnoreProperty(x=>x.RootPart)); + } + + + private SceneObjectGroup GetMySOG(string name) + { + SceneObjectGroup sog = FindSOG(name, region1); + if (sog == null) + { + sog = NewSOG(name, prim1, region1); + db.StoreObject(sog, region1); + } + return sog; + } + + // NOTE: it is a bad practice to rely on some of the previous tests having been run before. + // If the tests are run manually, one at a time, each starts with full class init (DB cleared). + // Even when all tests are run, NUnit 2.5+ no longer guarantee a specific test order. + // We shouldn't expect to find anything in the DB if we haven't put it there *in the same test*! + + [Test] + public void T020_PrimInventoryEmpty() + { +/* + TestHelpers.InMethod(); + + SceneObjectGroup sog = GetMySOG("object1"); + TaskInventoryItem t = sog.GetInventoryItem(sog.RootPart.LocalId, item1); + Assert.That(t, Is.Null); +*/ + } + + // TODO: Is there any point to call StorePrimInventory on a list, rather than on the prim itself? + + private void StoreInventory(SceneObjectGroup sog) + { + List list = new List(); + // TODO: seriously??? this is the way we need to loop to get this? + foreach (UUID uuid in sog.RootPart.Inventory.GetInventoryList()) + { + list.Add(sog.GetInventoryItem(sog.RootPart.LocalId, uuid)); + } + + db.StorePrimInventory(sog.RootPart.UUID, list); + } + + [Test] + public void T021_PrimInventoryBasic() + { +/* + TestHelpers.InMethod(); + + SceneObjectGroup sog = GetMySOG("object1"); + InventoryItemBase i = NewItem(item1, zero, zero, itemname1, zero); + + Assert.That(sog.AddInventoryItem(zero, sog.RootPart.LocalId, i, zero), Is.True); + TaskInventoryItem t = sog.GetInventoryItem(sog.RootPart.LocalId, item1); + Assert.That(t.Name, Is.EqualTo(itemname1), "Assert.That(t.Name, Is.EqualTo(itemname1))"); + + StoreInventory(sog); + + SceneObjectGroup sog1 = FindSOG("object1", region1); + Assert.That(sog1, Is.Not.Null); + + TaskInventoryItem t1 = sog1.GetInventoryItem(sog1.RootPart.LocalId, item1); + Assert.That(t1, Is.Not.Null); + Assert.That(t1.Name, Is.EqualTo(itemname1), "Assert.That(t.Name, Is.EqualTo(itemname1))"); + + // Updating inventory + t1.Name = "My New Name"; + sog1.UpdateInventoryItem(t1); + + StoreInventory(sog1); + + SceneObjectGroup sog2 = FindSOG("object1", region1); + TaskInventoryItem t2 = sog2.GetInventoryItem(sog2.RootPart.LocalId, item1); + Assert.That(t2.Name, Is.EqualTo("My New Name"), "Assert.That(t.Name, Is.EqualTo(\"My New Name\"))"); + + // Removing inventory + List list = new List(); + db.StorePrimInventory(prim1, list); + + sog = FindSOG("object1", region1); + t = sog.GetInventoryItem(sog.RootPart.LocalId, item1); + Assert.That(t, Is.Null); +*/ + } + + [Test] + public void T025_PrimInventoryPersistency() + { +/* + TestHelpers.InMethod(); + + InventoryItemBase i = new InventoryItemBase(); + UUID id = UUID.Random(); + i.ID = id; + UUID folder = UUID.Random(); + i.Folder = folder; + UUID owner = UUID.Random(); + i.Owner = owner; + UUID creator = UUID.Random(); + i.CreatorId = creator.ToString(); + string name = RandomName(); + i.Name = name; + i.Description = name; + UUID assetid = UUID.Random(); + i.AssetID = assetid; + int invtype = random.Next(); + i.InvType = invtype; + uint nextperm = (uint) random.Next(); + i.NextPermissions = nextperm; + uint curperm = (uint) random.Next(); + i.CurrentPermissions = curperm; + uint baseperm = (uint) random.Next(); + i.BasePermissions = baseperm; + uint eoperm = (uint) random.Next(); + i.EveryOnePermissions = eoperm; + int assettype = random.Next(); + i.AssetType = assettype; + UUID groupid = UUID.Random(); + i.GroupID = groupid; + bool groupown = true; + i.GroupOwned = groupown; + int saleprice = random.Next(); + i.SalePrice = saleprice; + byte saletype = (byte) random.Next(127); + i.SaleType = saletype; + uint flags = (uint) random.Next(); + i.Flags = flags; + int creationd = random.Next(); + i.CreationDate = creationd; + + SceneObjectGroup sog = GetMySOG("object1"); + Assert.That(sog.AddInventoryItem(zero, sog.RootPart.LocalId, i, zero), Is.True); + TaskInventoryItem t = sog.GetInventoryItem(sog.RootPart.LocalId, id); + + Assert.That(t.Name, Is.EqualTo(name), "Assert.That(t.Name, Is.EqualTo(name))"); + Assert.That(t.AssetID,Is.EqualTo(assetid), "Assert.That(t.AssetID,Is.EqualTo(assetid))"); + Assert.That(t.BasePermissions,Is.EqualTo(baseperm), "Assert.That(t.BasePermissions,Is.EqualTo(baseperm))"); + Assert.That(t.CreationDate,Is.EqualTo(creationd), "Assert.That(t.CreationDate,Is.EqualTo(creationd))"); + Assert.That(t.CreatorID,Is.EqualTo(creator), "Assert.That(t.CreatorID,Is.EqualTo(creator))"); + Assert.That(t.Description,Is.EqualTo(name), "Assert.That(t.Description,Is.EqualTo(name))"); + Assert.That(t.EveryonePermissions,Is.EqualTo(eoperm), "Assert.That(t.EveryonePermissions,Is.EqualTo(eoperm))"); + Assert.That(t.Flags,Is.EqualTo(flags), "Assert.That(t.Flags,Is.EqualTo(flags))"); + Assert.That(t.GroupID,Is.EqualTo(sog.RootPart.GroupID), "Assert.That(t.GroupID,Is.EqualTo(sog.RootPart.GroupID))"); + // Where is this group permissions?? + // Assert.That(t.GroupPermissions,Is.EqualTo(), "Assert.That(t.GroupPermissions,Is.EqualTo())"); + Assert.That(t.Type,Is.EqualTo(assettype), "Assert.That(t.Type,Is.EqualTo(assettype))"); + Assert.That(t.InvType, Is.EqualTo(invtype), "Assert.That(t.InvType, Is.EqualTo(invtype))"); + Assert.That(t.ItemID, Is.EqualTo(id), "Assert.That(t.ItemID, Is.EqualTo(id))"); + Assert.That(t.LastOwnerID, Is.EqualTo(sog.RootPart.LastOwnerID), "Assert.That(t.LastOwnerID, Is.EqualTo(sog.RootPart.LastOwnerID))"); + Assert.That(t.NextPermissions, Is.EqualTo(nextperm), "Assert.That(t.NextPermissions, Is.EqualTo(nextperm))"); + // Ownership changes when you drop an object into an object + // owned by someone else + Assert.That(t.OwnerID,Is.EqualTo(sog.RootPart.OwnerID), "Assert.That(t.OwnerID,Is.EqualTo(sog.RootPart.OwnerID))"); +// Assert.That(t.CurrentPermissions, Is.EqualTo(curperm | 16), "Assert.That(t.CurrentPermissions, Is.EqualTo(curperm | 8))"); + Assert.That(t.ParentID,Is.EqualTo(sog.RootPart.FolderID), "Assert.That(t.ParentID,Is.EqualTo(sog.RootPart.FolderID))"); + Assert.That(t.ParentPartID,Is.EqualTo(sog.RootPart.UUID), "Assert.That(t.ParentPartID,Is.EqualTo(sog.RootPart.UUID))"); +*/ + } +/* + [Test] + [ExpectedException(typeof(ArgumentException))] + public void T026_PrimInventoryMany() + { + + TestHelpers.InMethod(); + + UUID i1,i2,i3,i4; + i1 = UUID.Random(); + i2 = UUID.Random(); + i3 = UUID.Random(); + i4 = i3; + InventoryItemBase ib1 = NewItem(i1, zero, zero, RandomName(), zero); + InventoryItemBase ib2 = NewItem(i2, zero, zero, RandomName(), zero); + InventoryItemBase ib3 = NewItem(i3, zero, zero, RandomName(), zero); + InventoryItemBase ib4 = NewItem(i4, zero, zero, RandomName(), zero); + + SceneObjectGroup sog = FindSOG("object1", region1); + + Assert.That(sog.AddInventoryItem(zero, sog.RootPart.LocalId, ib1, zero), Is.True); + Assert.That(sog.AddInventoryItem(zero, sog.RootPart.LocalId, ib2, zero), Is.True); + Assert.That(sog.AddInventoryItem(zero, sog.RootPart.LocalId, ib3, zero), Is.True); + Assert.That(sog.AddInventoryItem(zero, sog.RootPart.LocalId, ib4, zero), Is.True); + + TaskInventoryItem t1 = sog.GetInventoryItem(sog.RootPart.LocalId, i1); + Assert.That(t1.Name, Is.EqualTo(ib1.Name), "Assert.That(t1.Name, Is.EqualTo(ib1.Name))"); + TaskInventoryItem t2 = sog.GetInventoryItem(sog.RootPart.LocalId, i2); + Assert.That(t2.Name, Is.EqualTo(ib2.Name), "Assert.That(t2.Name, Is.EqualTo(ib2.Name))"); + TaskInventoryItem t3 = sog.GetInventoryItem(sog.RootPart.LocalId, i3); + Assert.That(t3.Name, Is.EqualTo(ib3.Name), "Assert.That(t3.Name, Is.EqualTo(ib3.Name))"); + TaskInventoryItem t4 = sog.GetInventoryItem(sog.RootPart.LocalId, i4); + Assert.That(t4, Is.Null); + + } +*/ + [Test] + public void T052_RemoveObject() + { + TestHelpers.InMethod(); + + db.RemoveObject(prim1, region1); + SceneObjectGroup sog = FindSOG("object1", region1); + Assert.That(sog, Is.Null); + } + + [Test] + public void T100_DefaultRegionInfo() + { + TestHelpers.InMethod(); + + RegionSettings r1 = db.LoadRegionSettings(region1); + Assert.That(r1.RegionUUID, Is.EqualTo(region1), "Assert.That(r1.RegionUUID, Is.EqualTo(region1))"); + + RegionSettings r2 = db.LoadRegionSettings(region2); + Assert.That(r2.RegionUUID, Is.EqualTo(region2), "Assert.That(r2.RegionUUID, Is.EqualTo(region2))"); + } + + [Test] + public void T101_UpdateRegionInfo() + { + TestHelpers.InMethod(); + + int agentlimit = random.Next(); + double objectbonus = random.Next(); + int maturity = random.Next(); + UUID tertex1 = UUID.Random(); + UUID tertex2 = UUID.Random(); + UUID tertex3 = UUID.Random(); + UUID tertex4 = UUID.Random(); + double elev1nw = 1000; + double elev2nw = 1001; + double elev1ne = 1002; + double elev2ne = 1003; + double elev1se = 1004; + double elev2se = 1425; + double elev1sw = 352; + double elev2sw = 774; + double waterh = 125; + double terrainraise = 74; + double terrainlower = -79; + Vector3 sunvector = new Vector3((float)Math.Round(random.NextDouble(),5),(float)Math.Round(random.NextDouble(),5),(float)Math.Round(random.NextDouble(),5)); + UUID terimgid = UUID.Random(); + UUID cov = UUID.Random(); + + RegionSettings r1 = db.LoadRegionSettings(region1); + r1.BlockTerraform = true; + r1.BlockFly = true; + r1.AllowDamage = true; + r1.RestrictPushing = true; + r1.AllowLandResell = false; + r1.AllowLandJoinDivide = false; + r1.BlockShowInSearch = true; + r1.AgentLimit = agentlimit; + r1.ObjectBonus = objectbonus; + r1.Maturity = maturity; + r1.DisableScripts = true; + r1.DisableCollisions = true; + r1.DisablePhysics = true; + r1.TerrainTexture1 = tertex1; + r1.TerrainTexture2 = tertex2; + r1.TerrainTexture3 = tertex3; + r1.TerrainTexture4 = tertex4; + r1.Elevation1NW = elev1nw; + r1.Elevation2NW = elev2nw; + r1.Elevation1NE = elev1ne; + r1.Elevation2NE = elev2ne; + r1.Elevation1SE = elev1se; + r1.Elevation2SE = elev2se; + r1.Elevation1SW = elev1sw; + r1.Elevation2SW = elev2sw; + r1.WaterHeight = waterh; + r1.TerrainRaiseLimit = terrainraise; + r1.TerrainLowerLimit = terrainlower; + r1.UseEstateSun = false; + r1.Sandbox = true; + r1.TerrainImageID = terimgid; + r1.FixedSun = true; + r1.Covenant = cov; + + db.StoreRegionSettings(r1); + + RegionSettings r1a = db.LoadRegionSettings(region1); + Assert.That(r1a.RegionUUID, Is.EqualTo(region1), "Assert.That(r1a.RegionUUID, Is.EqualTo(region1))"); + Assert.That(r1a.BlockTerraform,Is.True); + Assert.That(r1a.BlockFly,Is.True); + Assert.That(r1a.AllowDamage,Is.True); + Assert.That(r1a.RestrictPushing,Is.True); + Assert.That(r1a.AllowLandResell,Is.False); + Assert.That(r1a.AllowLandJoinDivide,Is.False); + Assert.That(r1a.BlockShowInSearch,Is.True); + Assert.That(r1a.AgentLimit,Is.EqualTo(agentlimit), "Assert.That(r1a.AgentLimit,Is.EqualTo(agentlimit))"); + Assert.That(r1a.ObjectBonus,Is.EqualTo(objectbonus), "Assert.That(r1a.ObjectBonus,Is.EqualTo(objectbonus))"); + Assert.That(r1a.Maturity,Is.EqualTo(maturity), "Assert.That(r1a.Maturity,Is.EqualTo(maturity))"); + Assert.That(r1a.DisableScripts,Is.True); + Assert.That(r1a.DisableCollisions,Is.True); + Assert.That(r1a.DisablePhysics,Is.True); + Assert.That(r1a.TerrainTexture1,Is.EqualTo(tertex1), "Assert.That(r1a.TerrainTexture1,Is.EqualTo(tertex1))"); + Assert.That(r1a.TerrainTexture2,Is.EqualTo(tertex2), "Assert.That(r1a.TerrainTexture2,Is.EqualTo(tertex2))"); + Assert.That(r1a.TerrainTexture3,Is.EqualTo(tertex3), "Assert.That(r1a.TerrainTexture3,Is.EqualTo(tertex3))"); + Assert.That(r1a.TerrainTexture4,Is.EqualTo(tertex4), "Assert.That(r1a.TerrainTexture4,Is.EqualTo(tertex4))"); + Assert.That(r1a.Elevation1NW,Is.EqualTo(elev1nw), "Assert.That(r1a.Elevation1NW,Is.EqualTo(elev1nw))"); + Assert.That(r1a.Elevation2NW,Is.EqualTo(elev2nw), "Assert.That(r1a.Elevation2NW,Is.EqualTo(elev2nw))"); + Assert.That(r1a.Elevation1NE,Is.EqualTo(elev1ne), "Assert.That(r1a.Elevation1NE,Is.EqualTo(elev1ne))"); + Assert.That(r1a.Elevation2NE,Is.EqualTo(elev2ne), "Assert.That(r1a.Elevation2NE,Is.EqualTo(elev2ne))"); + Assert.That(r1a.Elevation1SE,Is.EqualTo(elev1se), "Assert.That(r1a.Elevation1SE,Is.EqualTo(elev1se))"); + Assert.That(r1a.Elevation2SE,Is.EqualTo(elev2se), "Assert.That(r1a.Elevation2SE,Is.EqualTo(elev2se))"); + Assert.That(r1a.Elevation1SW,Is.EqualTo(elev1sw), "Assert.That(r1a.Elevation1SW,Is.EqualTo(elev1sw))"); + Assert.That(r1a.Elevation2SW,Is.EqualTo(elev2sw), "Assert.That(r1a.Elevation2SW,Is.EqualTo(elev2sw))"); + Assert.That(r1a.WaterHeight,Is.EqualTo(waterh), "Assert.That(r1a.WaterHeight,Is.EqualTo(waterh))"); + Assert.That(r1a.TerrainRaiseLimit,Is.EqualTo(terrainraise), "Assert.That(r1a.TerrainRaiseLimit,Is.EqualTo(terrainraise))"); + Assert.That(r1a.TerrainLowerLimit,Is.EqualTo(terrainlower), "Assert.That(r1a.TerrainLowerLimit,Is.EqualTo(terrainlower))"); + Assert.That(r1a.Sandbox,Is.True); + //Assert.That(r1a.TerrainImageID,Is.EqualTo(terimgid), "Assert.That(r1a.TerrainImageID,Is.EqualTo(terimgid))"); + Assert.That(r1a.Covenant, Is.EqualTo(cov), "Assert.That(r1a.Covenant, Is.EqualTo(cov))"); + } + + [Test] + public void T300_NoTerrain() + { + TestHelpers.InMethod(); + + Assert.That(db.LoadTerrain(zero), Is.Null); + Assert.That(db.LoadTerrain(region1), Is.Null); + Assert.That(db.LoadTerrain(region2), Is.Null); + Assert.That(db.LoadTerrain(UUID.Random()), Is.Null); + } + + [Test] + public void T301_CreateTerrain() + { + TestHelpers.InMethod(); + + double[,] t1 = GenTerrain(height1); + db.StoreTerrain(t1, region1); + + // store terrain is async + Thread.Sleep(500); + + Assert.That(db.LoadTerrain(zero), Is.Null); + Assert.That(db.LoadTerrain(region1), Is.Not.Null); + Assert.That(db.LoadTerrain(region2), Is.Null); + Assert.That(db.LoadTerrain(UUID.Random()), Is.Null); + } + + [Test] + public void T302_FetchTerrain() + { + TestHelpers.InMethod(); + + double[,] baseterrain1 = GenTerrain(height1); + double[,] baseterrain2 = GenTerrain(height2); + double[,] t1 = db.LoadTerrain(region1); + Assert.That(CompareTerrain(t1, baseterrain1), Is.True); + Assert.That(CompareTerrain(t1, baseterrain2), Is.False); + } + + [Test] + public void T303_UpdateTerrain() + { + TestHelpers.InMethod(); + + double[,] baseterrain1 = GenTerrain(height1); + double[,] baseterrain2 = GenTerrain(height2); + db.StoreTerrain(baseterrain2, region1); + + // store terrain is async + Thread.Sleep(500); + + double[,] t1 = db.LoadTerrain(region1); + Assert.That(CompareTerrain(t1, baseterrain1), Is.False); + Assert.That(CompareTerrain(t1, baseterrain2), Is.True); + } + + [Test] + public void T400_EmptyLand() + { + TestHelpers.InMethod(); + + Assert.That(db.LoadLandObjects(zero).Count, Is.EqualTo(0), "Assert.That(db.LoadLandObjects(zero).Count, Is.EqualTo(0))"); + Assert.That(db.LoadLandObjects(region1).Count, Is.EqualTo(0), "Assert.That(db.LoadLandObjects(region1).Count, Is.EqualTo(0))"); + Assert.That(db.LoadLandObjects(region2).Count, Is.EqualTo(0), "Assert.That(db.LoadLandObjects(region2).Count, Is.EqualTo(0))"); + Assert.That(db.LoadLandObjects(UUID.Random()).Count, Is.EqualTo(0), "Assert.That(db.LoadLandObjects(UUID.Random()).Count, Is.EqualTo(0))"); + } + + // TODO: we should have real land tests, but Land is so + // intermingled with scene that you can't test it without a + // valid scene. That requires some disagregation. + + + //************************************************************************************// + // Extra private methods + + private double[,] GenTerrain(double value) + { + double[,] terret = new double[Constants.RegionSize, Constants.RegionSize]; + terret.Initialize(); + for (int x = 0; x < Constants.RegionSize; x++) + for (int y = 0; y < Constants.RegionSize; y++) + terret[x,y] = value; + + return terret; + } + + private bool CompareTerrain(double[,] one, double[,] two) + { + for (int x = 0; x < Constants.RegionSize; x++) + for (int y = 0; y < Constants.RegionSize; y++) + if (one[x,y] != two[x,y]) + return false; + + return true; + } + + private SceneObjectGroup FindSOG(string name, UUID r) + { + List objs = db.LoadObjects(r); + foreach (SceneObjectGroup sog in objs) + if (sog.Name == name) + return sog; + + return null; + } + + // This builds a minimalistic Prim, 1 SOG with 1 root SOP. A + // common failure case is people adding new fields that aren't + // initialized, but have non-null db constraints. We should + // honestly be passing more and more null things in here. + // + // Please note that in Sqlite.BuildPrim there is a commented out inline version + // of this so you can debug and step through the build process and check the fields + // + // Real World Value: Tests for situation where extending a SceneObjectGroup/SceneObjectPart + // causes the application to crash at the database layer because of null values + // in NOT NULL fields + // + private SceneObjectGroup NewSOG(string name, UUID uuid, UUID regionId) + { + RegionInfo regionInfo = new RegionInfo + { + RegionID = regionId, + RegionLocX = 0, + RegionLocY = 0 + }; + + SceneObjectPart sop = new SceneObjectPart + { + Name = name, + Description = name, + Text = RandomName(), + SitName = RandomName(), + TouchName = RandomName(), + UUID = uuid, + Shape = PrimitiveBaseShape.Default + }; + + SceneObjectGroup sog = new SceneObjectGroup(sop); + + return sog; + } + + private SceneObjectPart NewSOP(string name, UUID uuid) + { + SceneObjectPart sop = new SceneObjectPart + { + Name = name, + Description = name, + Text = RandomName(), + SitName = RandomName(), + TouchName = RandomName(), + UUID = uuid, + Shape = PrimitiveBaseShape.Default + }; + return sop; + } + + // These are copied from the Inventory Item tests + + private InventoryItemBase NewItem(UUID id, UUID parent, UUID owner, string name, UUID asset) + { + InventoryItemBase i = new InventoryItemBase + { + ID = id, + Folder = parent, + Owner = owner, + CreatorId = owner.ToString(), + Name = name, + Description = name, + AssetID = asset + }; + return i; + } + + private static string RandomName() + { + StringBuilder name = new StringBuilder(); + int size = random.Next(5,12); + char ch ; + for (int i=0; i\n\n 128\n 0\n 00000000-0000-0000-0000-000000000000\n 10\n 0\n 0\n 54ff9641-dd40-4a2c-b1f1-47dd3af24e50\n d740204e-bbbf-44aa-949d-02c7d739f6a5\n False\n AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n land data to test LandDataSerializer\n 536870944\n 2\n LandDataSerializerTest Land\n 0\n 0\n 1\n d4452578-2f25-4b97-a81b-819af559cfd7\n http://videos.opensimulator.org/bumblebee.mp4\n \n 1b8eedf9-6d15-448b-8015-24286f1756bf\n \n 0\n 0\n 0\n 00000000-0000-0000-0000-000000000000\n <0, 0, 0>\n <0, 0, 0>\n 0\n 0\n"; + private static string preSerializedWithParcelAccessList + = "\n\n 128\n 0\n 00000000-0000-0000-0000-000000000000\n 10\n 0\n 0\n 54ff9641-dd40-4a2c-b1f1-47dd3af24e50\n d740204e-bbbf-44aa-949d-02c7d739f6a5\n False\n AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=\n land data to test LandDataSerializer\n 536870944\n 2\n LandDataSerializerTest Land\n 0\n 0\n 1\n d4452578-2f25-4b97-a81b-819af559cfd7\n http://videos.opensimulator.org/bumblebee.mp4\n \n 1b8eedf9-6d15-448b-8015-24286f1756bf\n \n \n 62d65d45-c91a-4f77-862c-46557d978b6c\n \n 2\n \n \n ec2a8d18-2378-4fe0-8b68-2a31b57c481e\n \n 1\n \n \n 0\n 0\n 0\n 00000000-0000-0000-0000-000000000000\n <0, 0, 0>\n <0, 0, 0>\n 0\n 0\n"; + + [SetUp] + public void setup() + { + // setup LandData object + this.land = new LandData(); + this.land.AABBMax = new Vector3(1, 2, 3); + this.land.AABBMin = new Vector3(129, 130, 131); + this.land.Area = 128; + this.land.AuctionID = 4; + this.land.AuthBuyerID = new UUID("7176df0c-6c50-45db-8a37-5e78be56a0cd"); + this.land.Category = ParcelCategory.Residential; + this.land.ClaimDate = 1; + this.land.ClaimPrice = 2; + this.land.GlobalID = new UUID("54ff9641-dd40-4a2c-b1f1-47dd3af24e50"); + this.land.GroupID = new UUID("d740204e-bbbf-44aa-949d-02c7d739f6a5"); + this.land.Description = "land data to test LandDataSerializer"; + this.land.Flags = (uint)(ParcelFlags.AllowDamage | ParcelFlags.AllowVoiceChat); + this.land.LandingType = (byte)LandingType.Direct; + this.land.Name = "LandDataSerializerTest Land"; + this.land.Status = ParcelStatus.Leased; + this.land.LocalID = 1; + this.land.MediaAutoScale = (byte)0x01; + this.land.MediaID = new UUID("d4452578-2f25-4b97-a81b-819af559cfd7"); + this.land.MediaURL = "http://videos.opensimulator.org/bumblebee.mp4"; + this.land.OwnerID = new UUID("1b8eedf9-6d15-448b-8015-24286f1756bf"); + + this.landWithParcelAccessList = this.land.Copy(); + this.landWithParcelAccessList.ParcelAccessList.Clear(); + + LandAccessEntry pae0 = new LandAccessEntry(); + pae0.AgentID = new UUID("62d65d45-c91a-4f77-862c-46557d978b6c"); + pae0.Flags = AccessList.Ban; + pae0.Expires = 0; + this.landWithParcelAccessList.ParcelAccessList.Add(pae0); + + LandAccessEntry pae1 = new LandAccessEntry(); + pae1.AgentID = new UUID("ec2a8d18-2378-4fe0-8b68-2a31b57c481e"); + pae1.Flags = AccessList.Access; + pae1.Expires = 0; + this.landWithParcelAccessList.ParcelAccessList.Add(pae1); + } + + /// + /// Test the LandDataSerializer.Serialize() method + /// +// [Test] +// public void LandDataSerializerSerializeTest() +// { +// TestHelpers.InMethod(); +// +// string serialized = LandDataSerializer.Serialize(this.land).Replace("\r\n", "\n"); +// Assert.That(serialized.Length > 0, "Serialize(LandData) returned empty string"); +// +// // adding a simple boolean variable because resharper nUnit integration doesn't like this +// // XML data in the Assert.That statement. Not sure why. +// bool result = (serialized == preSerialized); +// Assert.That(result, "result of Serialize LandData does not match expected result"); +// +// string serializedWithParcelAccessList = LandDataSerializer.Serialize(this.landWithParcelAccessList).Replace("\r\n", "\n"); +// Assert.That(serializedWithParcelAccessList.Length > 0, +// "Serialize(LandData) returned empty string for LandData object with ParcelAccessList"); +// result = (serializedWithParcelAccessList == preSerializedWithParcelAccessList); +// Assert.That(result, +// "result of Serialize(LandData) does not match expected result (pre-serialized with parcel access list"); +// } + + /// + /// Test the LandDataSerializer.Deserialize() method + /// + [Test] + public void TestLandDataDeserializeNoAccessLists() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Dictionary options = new Dictionary(); + LandData ld = LandDataSerializer.Deserialize(LandDataSerializer.Serialize(this.land, options)); + Assert.That(ld, Is.Not.Null, "Deserialize(string) returned null"); +// Assert.That(ld.AABBMax, Is.EqualTo(land.AABBMax)); +// Assert.That(ld.AABBMin, Is.EqualTo(land.AABBMin)); + Assert.That(ld.Area, Is.EqualTo(land.Area)); + Assert.That(ld.AuctionID, Is.EqualTo(land.AuctionID)); + Assert.That(ld.AuthBuyerID, Is.EqualTo(land.AuthBuyerID)); + Assert.That(ld.Category, Is.EqualTo(land.Category)); + Assert.That(ld.ClaimDate, Is.EqualTo(land.ClaimDate)); + Assert.That(ld.ClaimPrice, Is.EqualTo(land.ClaimPrice)); + Assert.That(ld.GlobalID, Is.EqualTo(land.GlobalID), "Reified LandData.GlobalID != original LandData.GlobalID"); + Assert.That(ld.GroupID, Is.EqualTo(land.GroupID)); + Assert.That(ld.Description, Is.EqualTo(land.Description)); + Assert.That(ld.Flags, Is.EqualTo(land.Flags)); + Assert.That(ld.LandingType, Is.EqualTo(land.LandingType)); + Assert.That(ld.Name, Is.EqualTo(land.Name), "Reified LandData.Name != original LandData.Name"); + Assert.That(ld.Status, Is.EqualTo(land.Status)); + Assert.That(ld.LocalID, Is.EqualTo(land.LocalID)); + Assert.That(ld.MediaAutoScale, Is.EqualTo(land.MediaAutoScale)); + Assert.That(ld.MediaID, Is.EqualTo(land.MediaID)); + Assert.That(ld.MediaURL, Is.EqualTo(land.MediaURL)); + Assert.That(ld.OwnerID, Is.EqualTo(land.OwnerID)); + } + + [Test] + public void TestLandDataDeserializeWithAccessLists() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + LandData ld = LandDataSerializer.Deserialize(LandDataSerializerTest.preSerializedWithParcelAccessList); + Assert.That(ld != null, + "Deserialize(string) returned null (pre-serialized with parcel access list)"); + Assert.That(ld.GlobalID == this.landWithParcelAccessList.GlobalID, + "Reified LandData.GlobalID != original LandData.GlobalID (pre-serialized with parcel access list)"); + Assert.That(ld.Name == this.landWithParcelAccessList.Name, + "Reified LandData.Name != original LandData.Name (pre-serialized with parcel access list)"); + Assert.That(ld.ParcelAccessList.Count, Is.EqualTo(2)); + Assert.That(ld.ParcelAccessList[0].AgentID, Is.EqualTo(UUID.Parse("62d65d45-c91a-4f77-862c-46557d978b6c"))); + } + } +} diff --git a/Tests/OpenSim.Framework.Serialization.Tests/OpenSim.Framework.Serialization.Tests.csproj b/Tests/OpenSim.Framework.Serialization.Tests/OpenSim.Framework.Serialization.Tests.csproj new file mode 100644 index 00000000000..ae70d96cfdf --- /dev/null +++ b/Tests/OpenSim.Framework.Serialization.Tests/OpenSim.Framework.Serialization.Tests.csproj @@ -0,0 +1,49 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Framework.Serialization.Tests/RegionSettingsSerializerTests.cs b/Tests/OpenSim.Framework.Serialization.Tests/RegionSettingsSerializerTests.cs new file mode 100644 index 00000000000..2f6f0ca843f --- /dev/null +++ b/Tests/OpenSim.Framework.Serialization.Tests/RegionSettingsSerializerTests.cs @@ -0,0 +1,138 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework.Serialization.External; +using OpenSim.Tests.Common; + +namespace OpenSim.Framework.Serialization.Tests +{ + [TestFixture] + public class RegionSettingsSerializerTests : OpenSimTestCase + { + private string m_serializedRs = @" + + + True + True + True + True + True + True + True + True + True + 1 + True + 40 + 1.4 + + + 00000000-0000-0000-0000-000000000020 + 00000000-0000-0000-0000-000000000040 + 00000000-0000-0000-0000-000000000060 + 00000000-0000-0000-0000-000000000080 + 1.9 + 15.9 + 49 + 45.3 + 2.1 + 4.5 + 9.2 + 19.2 + + + 23 + 17.9 + 0.4 + True + true + 12 + + + 00000000-0000-0000-0000-111111111111 + 1,-2,0.33 + +"; + + private RegionSettings m_rs; + + [SetUp] + public void Setup() + { + m_rs = new RegionSettings(); + m_rs.AgentLimit = 17; + m_rs.AllowDamage = true; + m_rs.AllowLandJoinDivide = true; + m_rs.AllowLandResell = true; + m_rs.BlockFly = true; + m_rs.BlockShowInSearch = true; + m_rs.BlockTerraform = true; + m_rs.DisableCollisions = true; + m_rs.DisablePhysics = true; + m_rs.DisableScripts = true; + m_rs.Elevation1NW = 15.9; + m_rs.Elevation1NE = 45.3; + m_rs.Elevation1SE = 49; + m_rs.Elevation1SW = 1.9; + m_rs.Elevation2NW = 4.5; + m_rs.Elevation2NE = 19.2; + m_rs.Elevation2SE = 9.2; + m_rs.Elevation2SW = 2.1; + m_rs.FixedSun = true; + m_rs.SunPosition = 12.0; + m_rs.ObjectBonus = 1.4; + m_rs.RestrictPushing = true; + m_rs.TerrainLowerLimit = 0.4; + m_rs.TerrainRaiseLimit = 17.9; + m_rs.TerrainTexture1 = UUID.Parse("00000000-0000-0000-0000-000000000020"); + m_rs.TerrainTexture2 = UUID.Parse("00000000-0000-0000-0000-000000000040"); + m_rs.TerrainTexture3 = UUID.Parse("00000000-0000-0000-0000-000000000060"); + m_rs.TerrainTexture4 = UUID.Parse("00000000-0000-0000-0000-000000000080"); + m_rs.UseEstateSun = true; + m_rs.WaterHeight = 23; + m_rs.TelehubObject = UUID.Parse("00000000-0000-0000-0000-111111111111"); + m_rs.AddSpawnPoint(SpawnPoint.Parse("1,-2,0.33")); + } + + [Test] + public void TestRegionSettingsDeserialize() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + RegionSettings deserRs = RegionSettingsSerializer.Deserialize(m_serializedRs, out ViewerEnvironment dummy, new EstateSettings()); + Assert.That(deserRs, Is.Not.Null); + Assert.That(deserRs.TerrainTexture2, Is.EqualTo(m_rs.TerrainTexture2)); + Assert.That(deserRs.DisablePhysics, Is.EqualTo(m_rs.DisablePhysics)); + Assert.That(deserRs.TerrainLowerLimit, Is.EqualTo(m_rs.TerrainLowerLimit)); + Assert.That(deserRs.TelehubObject, Is.EqualTo(m_rs.TelehubObject)); + Assert.That(deserRs.SpawnPoints()[0].ToString(), Is.EqualTo(m_rs.SpawnPoints()[0].ToString())); + } + } +} diff --git a/Tests/OpenSim.Framework.Servers.Tests/OpenSim.Framework.Servers.Tests.csproj b/Tests/OpenSim.Framework.Servers.Tests/OpenSim.Framework.Servers.Tests.csproj new file mode 100644 index 00000000000..5b35f44dcb5 --- /dev/null +++ b/Tests/OpenSim.Framework.Servers.Tests/OpenSim.Framework.Servers.Tests.csproj @@ -0,0 +1,36 @@ + + + net8.0 + enable + enable + false + true + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Framework.Servers.Tests/VersionInfoTests.cs b/Tests/OpenSim.Framework.Servers.Tests/VersionInfoTests.cs new file mode 100644 index 00000000000..68a1c78aa4b --- /dev/null +++ b/Tests/OpenSim.Framework.Servers.Tests/VersionInfoTests.cs @@ -0,0 +1,45 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Framework.Servers.Tests +{ + [TestFixture] + public class VersionInfoTests : OpenSimTestCase + { + [Test] + public void TestVersionLength() + { + Assert.AreEqual(VersionInfo.VERSIONINFO_VERSION_LENGTH, VersionInfo.Version.Length," VersionInfo.Version string not " + VersionInfo.VERSIONINFO_VERSION_LENGTH + " chars."); + } + } +} diff --git a/Tests/OpenSim.Framework.Tests/AgentCircuitDataTest.cs b/Tests/OpenSim.Framework.Tests/AgentCircuitDataTest.cs new file mode 100644 index 00000000000..0e25d189bd7 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/AgentCircuitDataTest.cs @@ -0,0 +1,350 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace OpenSim.Framework.Tests +{ + [TestFixture] + public class AgentCircuitDataTest : IDisposable + { + private UUID AgentId; + private AvatarAppearance AvAppearance; + private byte[] VisualParams; + private UUID BaseFolder; + private string CapsPath; + private Dictionary ChildrenCapsPaths; + private uint circuitcode = 0949030; + private string firstname; + private string lastname; + private UUID SecureSessionId; + private UUID SessionId; + private Vector3 StartPos; + + public AgentCircuitDataTest() + { + } + + [SetUp] + public void Setup() + { + AgentId = UUID.Random(); + BaseFolder = UUID.Random(); + CapsPath = "http://www.opensimulator.org/Caps/Foo"; + ChildrenCapsPaths = new Dictionary(); + ChildrenCapsPaths.Add(ulong.MaxValue, "http://www.opensimulator.org/Caps/Foo2"); + firstname = "CoolAvatarTest"; + lastname = "test"; + StartPos = new Vector3(5,23,125); + + SecureSessionId = UUID.Random(); + SessionId = UUID.Random(); + + AvAppearance = new AvatarAppearance(); + VisualParams = new byte[218]; + + //body + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HEIGHT] = 155; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_THICKNESS] = 00; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BODY_FAT] = 0; + + //Torso + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_TORSO_MUSCLES] = 48; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_NECK_THICKNESS] = 43; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_NECK_LENGTH] = 255; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_SHOULDERS] = 94; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_CHEST_MALE_NO_PECS] = 199; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_ARM_LENGTH] = 255; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HAND_SIZE] = 33; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_TORSO_LENGTH] = 240; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LOVE_HANDLES] = 0; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BELLY_SIZE] = 0; + + // legs + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LEG_MUSCLES] = 82; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LEG_LENGTH] = 255; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HIP_WIDTH] = 84; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HIP_LENGTH] = 166; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BUTT_SIZE] = 64; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_SADDLEBAGS] = 89; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BOWED_LEGS] = 127; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_FOOT_SIZE] = 45; + + + // head + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HEAD_SIZE] = 255; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_SQUASH_STRETCH_HEAD] = 0; // head stretch + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HEAD_SHAPE] = 155; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EGG_HEAD] = 127; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_POINTY_EARS] = 255; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HEAD_LENGTH] = 45; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_FACE_SHEAR] = 127; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_FOREHEAD_ANGLE] = 104; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BIG_BROW] = 94; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_PUFFY_UPPER_CHEEKS] = 0; // upper cheeks + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_DOUBLE_CHIN] = 122; // lower cheeks + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_HIGH_CHEEK_BONES] = 130; + + // eyes + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EYE_SIZE] = 105; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_WIDE_EYES] = 135; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EYE_SPACING] = 184; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EYELID_CORNER_UP] = 230; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EYELID_INNER_CORNER_UP] = 120; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EYE_DEPTH] = 158; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_UPPER_EYELID_FOLD] = 69; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BAGGY_EYES] = 38; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EYELASHES_LONG] = 127; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_POP_EYE] = 127; + + VisualParams[(int)AvatarAppearance.VPElement.EYES_EYE_COLOR] = 25; + VisualParams[(int)AvatarAppearance.VPElement.EYES_EYE_LIGHTNESS] = 127; + + // ears + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BIG_EARS] = 255; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_EARS_OUT] = 127; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_ATTACHED_EARLOBES] = 127; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_POINTY_EARS] = 255; + + // nose + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_NOSE_BIG_OUT] = 79; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_WIDE_NOSE] = 35; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BROAD_NOSTRILS] = 86; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LOW_SEPTUM_NOSE] = 112; // nostril division + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BULBOUS_NOSE] = 25; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_NOBLE_NOSE_BRIDGE] = 25; // upper bridge + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LOWER_BRIDGE_NOSE] = 25; // lower bridge + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_WIDE_NOSE_BRIDGE] = 25; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_UPTURNED_NOSE_TIP] = 107; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_BULBOUS_NOSE_TIP] = 25; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_CROOKED_NOSE] = 127; + + // Mouth + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LIP_WIDTH] = 122; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_TALL_LIPS] = 10; // lip fullness + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LIP_THICKNESS] = 112; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LIP_RATIO] = 137; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_MOUTH_HEIGHT] = 176; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_MOUTH_CORNER] = 140; // Sad --> happy + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_LIP_CLEFT_DEEP] = 84; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_WIDE_LIP_CLEFT] = 84; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_SHIFT_MOUTH] = 127; + + // chin + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_WEAK_CHIN] = 119; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_SQUARE_JAW] = 5; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_DEEP_CHIN] = 132; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_JAW_ANGLE] = 153; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_JAW_JUT] = 100; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_JOWLS] = 38; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_CLEFT_CHIN] = 89; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_CLEFT_CHIN_UPPER] = 89; + VisualParams[(int)AvatarAppearance.VPElement.SHAPE_DOUBLE_CHIN] = 0; + + // hair color + VisualParams[(int)AvatarAppearance.VPElement.HAIR_WHITE_HAIR] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_RAINBOW_COLOR_39] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_BLONDE_HAIR] = 24; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_RED_HAIR] = 0; + + // hair style + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_VOLUME] = 160; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_FRONT] = 153; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_SIDES] = 153; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_BACK] = 170; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_BIG_FRONT] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_BIG_TOP] = 117; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_BIG_BACK] = 170; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_FRONT_FRINGE] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_SIDE_FRINGE] = 142; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_BACK_FRINGE] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_SIDES_FULL] = 146; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_SWEEP] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_SHEAR_FRONT] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_SHEAR_BACK] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_TAPER_FRONT] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_TAPER_BACK] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_RUMPLED] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_PIGTAILS] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_PONYTAIL] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_SPIKED] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_TILT] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_PART_MIDDLE] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_PART_RIGHT] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_PART_LEFT] = 0; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_BANGS_PART_MIDDLE] = 155; + + //Eyebrows + VisualParams[(int)AvatarAppearance.VPElement.HAIR_EYEBROW_SIZE] = 20; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_EYEBROW_DENSITY] = 140; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_LOWER_EYEBROWS] = 200; // eyebrow height + VisualParams[(int)AvatarAppearance.VPElement.HAIR_ARCED_EYEBROWS] = 124; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_POINTY_EYEBROWS] = 65; + + //Facial hair + VisualParams[(int)AvatarAppearance.VPElement.HAIR_HAIR_THICKNESS] = 65; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_SIDEBURNS] = 235; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_MOUSTACHE] = 75; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_CHIN_CURTAINS] = 140; + VisualParams[(int)AvatarAppearance.VPElement.HAIR_SOULPATCH] = 0; + + AvAppearance.VisualParams = VisualParams; + + AvAppearance.SetAppearance(AvAppearance.Texture, (byte[])VisualParams.Clone()); + } + + public void Dispose() + { + //throw new NotImplementedException(); + } + + /// + /// Test to ensure that the serialization format is the same and the underlying types don't change without notice + /// oldSerialization is just a json serialization of the OSDMap packed for the AgentCircuitData. + /// The idea is that if the current json serializer cannot parse the old serialization, then the underlying types + /// have changed and are incompatible. + /// + [Test] + public void HistoricalAgentCircuitDataOSDConversion() + { + string oldSerialization = "{\"agent_id\":\"522675bd-8214-40c1-b3ca-9c7f7fd170be\",\"base_folder\":\"c40b5f5f-476f-496b-bd69-b5a539c434d8\",\"caps_path\":\"http://www.opensimulator.org/Caps/Foo\",\"children_seeds\":[{\"handle\":\"18446744073709551615\",\"seed\":\"http://www.opensimulator.org/Caps/Foo2\"}],\"child\":false,\"circuit_code\":\"949030\",\"first_name\":\"CoolAvatarTest\",\"last_name\":\"test\",\"inventory_folder\":\"c40b5f5f-476f-496b-bd69-b5a539c434d8\",\"secure_session_id\":\"1e608e2b-0ddb-41f6-be0f-926f61cd3e0a\",\"session_id\":\"aa06f798-9d70-4bdb-9bbf-012a02ee2baf\",\"start_pos\":\"<5, 23, 125>\"}"; + AgentCircuitData Agent1Data = new AgentCircuitData(); + Agent1Data.AgentID = new UUID("522675bd-8214-40c1-b3ca-9c7f7fd170be"); + Agent1Data.Appearance = AvAppearance; + Agent1Data.BaseFolder = new UUID("c40b5f5f-476f-496b-bd69-b5a539c434d8"); + Agent1Data.CapsPath = CapsPath; + Agent1Data.child = false; + Agent1Data.ChildrenCapSeeds = ChildrenCapsPaths; + Agent1Data.circuitcode = circuitcode; + Agent1Data.firstname = firstname; + Agent1Data.InventoryFolder = new UUID("c40b5f5f-476f-496b-bd69-b5a539c434d8"); + Agent1Data.lastname = lastname; + Agent1Data.SecureSessionID = new UUID("1e608e2b-0ddb-41f6-be0f-926f61cd3e0a"); + Agent1Data.SessionID = new UUID("aa06f798-9d70-4bdb-9bbf-012a02ee2baf"); + Agent1Data.startpos = StartPos; + + OSDMap map2; + + try + { + map2 = (OSDMap) OSDParser.DeserializeJson(oldSerialization); + + + AgentCircuitData Agent2Data = new AgentCircuitData(); + Agent2Data.UnpackAgentCircuitData(map2); + + Assert.Equals(Agent1Data.AgentID, Agent2Data.AgentID); + Assert.Equals(Agent1Data.BaseFolder, Agent2Data.BaseFolder); + + Assert.Equals(Agent1Data.CapsPath, Agent2Data.CapsPath); + Assert.Equals(Agent1Data.child, Agent2Data.child); + Assert.Equals(Agent1Data.ChildrenCapSeeds.Count, Agent2Data.ChildrenCapSeeds.Count); + Assert.Equals(Agent1Data.circuitcode, Agent2Data.circuitcode); + Assert.Equals(Agent1Data.firstname, Agent2Data.firstname); + Assert.Equals(Agent1Data.InventoryFolder, Agent2Data.InventoryFolder); + Assert.Equals(Agent1Data.lastname, Agent2Data.lastname); + Assert.Equals(Agent1Data.SecureSessionID, Agent2Data.SecureSessionID); + Assert.Equals(Agent1Data.SessionID, Agent2Data.SessionID); + Assert.Equals(Agent1Data.startpos, Agent2Data.startpos); + } + catch (LitJson.JsonException) + { + //intermittant litjson errors :P + Assert.Equals(1,1); + } + /* + Enable this once VisualParams go in the packing method + for (int i=0;i<208;i++) + Assert.True((Agent1Data.Appearance.VisualParams[i] == Agent2Data.Appearance.VisualParams[i])); + */ + } + + /// + /// Test to ensure that the packing and unpacking methods work. + /// + [Test] + public void TestAgentCircuitDataOSDConversion() + { + AgentCircuitData Agent1Data = new AgentCircuitData(); + Agent1Data.AgentID = AgentId; + Agent1Data.Appearance = AvAppearance; + Agent1Data.BaseFolder = BaseFolder; + Agent1Data.CapsPath = CapsPath; + Agent1Data.child = false; + Agent1Data.ChildrenCapSeeds = ChildrenCapsPaths; + Agent1Data.circuitcode = circuitcode; + Agent1Data.firstname = firstname; + Agent1Data.InventoryFolder = BaseFolder; + Agent1Data.lastname = lastname; + Agent1Data.SecureSessionID = SecureSessionId; + Agent1Data.SessionID = SessionId; + Agent1Data.startpos = StartPos; + + EntityTransferContext ctx = new EntityTransferContext(); + OSDMap map2; + OSDMap map = Agent1Data.PackAgentCircuitData(ctx); + + try + { + string str = OSDParser.SerializeJsonString(map); + map2 = (OSDMap) OSDParser.DeserializeJson(str); + } + catch (System.NullReferenceException) + { + //spurious litjson errors :P + map2 = map; + Assert.Equals(1,1); + return; + } + + AgentCircuitData Agent2Data = new AgentCircuitData(); + Agent2Data.UnpackAgentCircuitData(map2); + + Assert.Equals(Agent1Data.AgentID, Agent2Data.AgentID); + Assert.Equals(Agent1Data.BaseFolder, Agent2Data.BaseFolder); + + Assert.Equals(Agent1Data.CapsPath, Agent2Data.CapsPath); + Assert.Equals(Agent1Data.child, Agent2Data.child); + Assert.Equals(Agent1Data.ChildrenCapSeeds.Count, Agent2Data.ChildrenCapSeeds.Count); + Assert.Equals(Agent1Data.circuitcode, Agent2Data.circuitcode); + Assert.Equals(Agent1Data.firstname, Agent2Data.firstname); + Assert.Equals(Agent1Data.InventoryFolder, Agent2Data.InventoryFolder); + Assert.Equals(Agent1Data.lastname, Agent2Data.lastname); + Assert.Equals(Agent1Data.SecureSessionID, Agent2Data.SecureSessionID); + Assert.Equals(Agent1Data.SessionID, Agent2Data.SessionID); + Assert.Equals(Agent1Data.startpos, Agent2Data.startpos); + + /* + Enable this once VisualParams go in the packing method + for (int i = 0; i < 208; i++) + Assert.True((Agent1Data.Appearance.VisualParams[i] == Agent2Data.Appearance.VisualParams[i])); + */ + } + } +} diff --git a/Tests/OpenSim.Framework.Tests/AgentCircuitManagerTests.cs b/Tests/OpenSim.Framework.Tests/AgentCircuitManagerTests.cs new file mode 100644 index 00000000000..419173a8a11 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/AgentCircuitManagerTests.cs @@ -0,0 +1,205 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +namespace OpenSim.Framework.Tests +{ + public class AgentCircuitManagerTests : IDisposable + { + private AgentCircuitData m_agentCircuitData1; + private AgentCircuitData m_agentCircuitData2; + private UUID AgentId1; + private UUID AgentId2; + private uint circuitcode1; + private uint circuitcode2; + + private UUID SessionId1; + private UUID SessionId2; + private Random rnd = new Random(Environment.TickCount); + + public AgentCircuitManagerTests() + { + } + + [SetUp] + public void Setup() + { + AgentId1 = UUID.Random(); + AgentId2 = UUID.Random(); + circuitcode1 = (uint) rnd.Next((int)uint.MinValue, int.MaxValue); + circuitcode2 = (uint) rnd.Next((int)uint.MinValue, int.MaxValue); + SessionId1 = UUID.Random(); + SessionId2 = UUID.Random(); + UUID BaseFolder = UUID.Random(); + string CapsPath = "http://www.opensimulator.org/Caps/Foo"; + Dictionary ChildrenCapsPaths = new Dictionary(); + ChildrenCapsPaths.Add(ulong.MaxValue, "http://www.opensimulator.org/Caps/Foo2"); + string firstname = "CoolAvatarTest"; + string lastname = "test"; + Vector3 StartPos = new Vector3(5, 23, 125); + + UUID SecureSessionId = UUID.Random(); + // TODO: unused: UUID SessionId = UUID.Random(); + + m_agentCircuitData1 = new AgentCircuitData(); + m_agentCircuitData1.AgentID = AgentId1; + m_agentCircuitData1.Appearance = new AvatarAppearance(); + m_agentCircuitData1.BaseFolder = BaseFolder; + m_agentCircuitData1.CapsPath = CapsPath; + m_agentCircuitData1.child = false; + m_agentCircuitData1.ChildrenCapSeeds = ChildrenCapsPaths; + m_agentCircuitData1.circuitcode = circuitcode1; + m_agentCircuitData1.firstname = firstname; + m_agentCircuitData1.InventoryFolder = BaseFolder; + m_agentCircuitData1.lastname = lastname; + m_agentCircuitData1.SecureSessionID = SecureSessionId; + m_agentCircuitData1.SessionID = SessionId1; + m_agentCircuitData1.startpos = StartPos; + + m_agentCircuitData2 = new AgentCircuitData(); + m_agentCircuitData2.AgentID = AgentId2; + m_agentCircuitData2.Appearance = new AvatarAppearance(); + m_agentCircuitData2.BaseFolder = BaseFolder; + m_agentCircuitData2.CapsPath = CapsPath; + m_agentCircuitData2.child = false; + m_agentCircuitData2.ChildrenCapSeeds = ChildrenCapsPaths; + m_agentCircuitData2.circuitcode = circuitcode2; + m_agentCircuitData2.firstname = firstname; + m_agentCircuitData2.InventoryFolder = BaseFolder; + m_agentCircuitData2.lastname = lastname; + m_agentCircuitData2.SecureSessionID = SecureSessionId; + m_agentCircuitData2.SessionID = SessionId2; + m_agentCircuitData2.startpos = StartPos; + } + + public void Dispose() + { + + } + + /// + /// Validate that adding the circuit works appropriately + /// + [Test] + public void AddAgentCircuitTest() + { + AgentCircuitManager agentCircuitManager = new AgentCircuitManager(); + agentCircuitManager.AddNewCircuit(circuitcode1,m_agentCircuitData1); + agentCircuitManager.AddNewCircuit(circuitcode2, m_agentCircuitData2); + AgentCircuitData agent = agentCircuitManager.GetAgentCircuitData(circuitcode1); + + Assert.That(m_agentCircuitData1.AgentID, Is.EqualTo(agent.AgentID)); + Assert.That(m_agentCircuitData1.BaseFolder, Is.EqualTo(agent.BaseFolder)); + + Assert.That(m_agentCircuitData1.CapsPath, Is.EqualTo(agent.CapsPath)); + Assert.That(m_agentCircuitData1.child, Is.EqualTo(agent.child)); + Assert.That(m_agentCircuitData1.ChildrenCapSeeds, Is.EqualTo(agent.ChildrenCapSeeds)); + Assert.That(m_agentCircuitData1.circuitcode, Is.EqualTo(agent.circuitcode)); + Assert.That(m_agentCircuitData1.firstname, Is.EqualTo(agent.firstname)); + Assert.That(m_agentCircuitData1.InventoryFolder, Is.EqualTo(agent.InventoryFolder)); + Assert.That(m_agentCircuitData1.lastname, Is.EqualTo(agent.lastname)); + Assert.That(m_agentCircuitData1.SecureSessionID, Is.EqualTo(agent.SecureSessionID)); + Assert.That(m_agentCircuitData1.SessionID, Is.EqualTo(agent.SessionID)); + Assert.That(m_agentCircuitData1.startpos, Is.EqualTo(agent.startpos)); + } + + /// + /// Validate that removing the circuit code removes it appropriately + /// + [Test] + public void RemoveAgentCircuitTest() + { + AgentCircuitManager agentCircuitManager = new AgentCircuitManager(); + + agentCircuitManager.AddNewCircuit(circuitcode1, m_agentCircuitData1); + agentCircuitManager.AddNewCircuit(circuitcode2, m_agentCircuitData2); + agentCircuitManager.RemoveCircuit(circuitcode2); + + AgentCircuitData agent = agentCircuitManager.GetAgentCircuitData(circuitcode2); + Assert.That(agent, Is.EqualTo(null)); + + } + + /// + /// Validate that changing the circuit code works + /// + [Test] + public void ChangeAgentCircuitCodeTest() + { + AgentCircuitManager agentCircuitManager = new AgentCircuitManager(); + agentCircuitManager.AddNewCircuit(circuitcode1, m_agentCircuitData1); + agentCircuitManager.AddNewCircuit(circuitcode2, m_agentCircuitData2); + bool result = false; + + result = agentCircuitManager.TryChangeCircuitCode(circuitcode1, 393930); + + AgentCircuitData agent = agentCircuitManager.GetAgentCircuitData(393930); + AgentCircuitData agent2 = agentCircuitManager.GetAgentCircuitData(circuitcode1); + + Assert.That(agent != null); + Assert.That(agent2 == null); + Assert.That(result); + + } + + /// + /// Validates that the login authentication scheme is working + /// First one should be authorized + /// Rest should not be authorized + /// + [Test] + public void ValidateLoginTest() + { + AgentCircuitManager agentCircuitManager = new AgentCircuitManager(); + agentCircuitManager.AddNewCircuit(circuitcode1, m_agentCircuitData1); + agentCircuitManager.AddNewCircuit(circuitcode2, m_agentCircuitData2); + + // should be authorized + AuthenticateResponse resp = agentCircuitManager.AuthenticateSession(SessionId1, AgentId1, circuitcode1); + Assert.That(resp.Authorised); + + //should not be authorized + resp = agentCircuitManager.AuthenticateSession(SessionId1, UUID.Random(), circuitcode1); + Assert.That(!resp.Authorised); + + resp = agentCircuitManager.AuthenticateSession(UUID.Random(), AgentId1, circuitcode1); + Assert.That(!resp.Authorised); + + resp = agentCircuitManager.AuthenticateSession(SessionId1, AgentId1, circuitcode2); + Assert.That(!resp.Authorised); + + resp = agentCircuitManager.AuthenticateSession(SessionId2, AgentId1, circuitcode2); + Assert.That(!resp.Authorised); + + agentCircuitManager.RemoveCircuit(circuitcode2); + + resp = agentCircuitManager.AuthenticateSession(SessionId2, AgentId2, circuitcode2); + Assert.That(!resp.Authorised); + } + } +} diff --git a/Tests/OpenSim.Framework.Tests/AnimationTests.cs b/Tests/OpenSim.Framework.Tests/AnimationTests.cs new file mode 100644 index 00000000000..23e9cb764de --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/AnimationTests.cs @@ -0,0 +1,93 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +namespace OpenSim.Framework.Tests +{ + public class AnimationTests : IDisposable + { + private Animation anim1; + private Animation anim2; + private UUID animUUID1 = UUID.Zero; + private UUID objUUID1 = UUID.Zero; + private UUID animUUID2 = UUID.Zero; + private UUID objUUID2 = UUID.Zero; + + public AnimationTests() + { + } + + [SetUp] + public void Setup() + { + animUUID1 = UUID.Random(); + animUUID2 = UUID.Random(); + objUUID1 = UUID.Random(); + objUUID2 = UUID.Random(); + + anim1 = new Animation(animUUID1, 1, objUUID1); + anim2 = new Animation(animUUID2, 1, objUUID2); + } + + public void Dispose() + { + // throw new NotImplementedException(); + } + + [Test] + public void AnimationOSDTest() + { + Assert.That(anim1.AnimID==animUUID1 && anim1.ObjectID == objUUID1 && anim1.SequenceNum ==1, "The Animation Constructor didn't set the fields correctly"); + OSD updateMessage = anim1.PackUpdateMessage(); + Assert.That(updateMessage is OSDMap, "Packed UpdateMessage isn't an OSDMap"); + OSDMap updateMap = (OSDMap) updateMessage; + Assert.That(updateMap.ContainsKey("animation"), "Packed Message doesn't contain an animation element"); + Assert.That(updateMap.ContainsKey("object_id"), "Packed Message doesn't contain an object_id element"); + Assert.That(updateMap.ContainsKey("seq_num"), "Packed Message doesn't contain a seq_num element"); + Assert.That(updateMap["animation"].AsUUID() == animUUID1); + Assert.That(updateMap["object_id"].AsUUID() == objUUID1); + Assert.That(updateMap["seq_num"].AsInteger() == 1); + + Animation anim3 = new Animation(updateMap); + + Assert.That(anim3.ObjectID == anim1.ObjectID && anim3.AnimID == anim1.AnimID && anim3.SequenceNum == anim1.SequenceNum, "OSDMap Constructor failed to set the properties correctly."); + + anim3.UnpackUpdateMessage(anim2.PackUpdateMessage()); + + Assert.That(anim3.ObjectID == objUUID2 && anim3.AnimID == animUUID2 && anim3.SequenceNum == 1, "Animation.UnpackUpdateMessage failed to set the properties correctly."); + + Animation anim4 = new Animation(); + anim4.AnimID = anim2.AnimID; + anim4.ObjectID = anim2.ObjectID; + anim4.SequenceNum = anim2.SequenceNum; + + Assert.That(anim4.ObjectID == objUUID2 && anim4.AnimID == animUUID2 && anim4.SequenceNum == 1, "void constructor and manual field population failed to set the properties correctly."); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Framework.Tests/AssetBaseTest.cs b/Tests/OpenSim.Framework.Tests/AssetBaseTest.cs new file mode 100644 index 00000000000..5fa885a3f75 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/AssetBaseTest.cs @@ -0,0 +1,75 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +namespace OpenSim.Framework.Tests +{ + public class AssetBaseTest : IDisposable + { + public AssetBaseTest() + { + } + + public void Dispose() + { + // throw new NotImplementedException(); + } + + [Test] + public void TestContainsReferences() + { + CheckContainsReferences(AssetType.Bodypart, true); + CheckContainsReferences(AssetType.Clothing, true); + + CheckContainsReferences(AssetType.Animation, false); + CheckContainsReferences(AssetType.CallingCard, false); + CheckContainsReferences(AssetType.Folder , false); + CheckContainsReferences(AssetType.Gesture , false); + CheckContainsReferences(AssetType.ImageJPEG , false); + CheckContainsReferences(AssetType.ImageTGA , false); + CheckContainsReferences(AssetType.Landmark , false); + CheckContainsReferences(AssetType.LSLBytecode, false); + CheckContainsReferences(AssetType.LSLText, false); + CheckContainsReferences(AssetType.Notecard, false); + CheckContainsReferences(AssetType.Object, false); + CheckContainsReferences(AssetType.Simstate, false); + CheckContainsReferences(AssetType.Sound, false); + CheckContainsReferences(AssetType.SoundWAV, false); + CheckContainsReferences(AssetType.Texture, false); + CheckContainsReferences(AssetType.TextureTGA, false); + CheckContainsReferences(AssetType.Unknown, false); + } + + private void CheckContainsReferences(AssetType assetType, bool expected) + { + AssetBase asset = new AssetBase(UUID.Zero, String.Empty, (sbyte)assetType, UUID.Zero.ToString()); + bool actual = asset.ContainsReferences; + Assert.That(expected == actual, $"Expected {assetType}.ContainsReferences to be {expected} but was {actual}."); + } + } +} diff --git a/Tests/OpenSim.Framework.Tests/CacheTests.cs b/Tests/OpenSim.Framework.Tests/CacheTests.cs new file mode 100644 index 00000000000..043e3821226 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/CacheTests.cs @@ -0,0 +1,130 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + + +namespace OpenSim.Framework.Tests +{ + public class CacheTests : IDisposable + { + private Cache cache; + private UUID cacheItemUUID; + + public CacheTests() + { + } + + [SetUp] + public void Setup() + { + cache = new Cache(); + cache = new Cache(CacheMedium.Memory,CacheStrategy.Aggressive,CacheFlags.AllowUpdate); + cacheItemUUID = UUID.Random(); + MemoryCacheItem cachedItem = new MemoryCacheItem(cacheItemUUID.ToString(),DateTime.Now + TimeSpan.FromDays(1)); + byte[] foo = new byte[1]; + foo[0] = 255; + cachedItem.Store(foo); + cache.Store(cacheItemUUID.ToString(), cachedItem); + } + + public void Dispose() + { + throw new NotImplementedException(); + } + + [Test] + public void TestRetreive() + { + CacheItemBase citem = (CacheItemBase)cache.Get(cacheItemUUID.ToString()); + byte[] data = (byte[]) citem.Retrieve(); + Assert.That(data.Length == 1, "Cached Item should have one byte element"); + Assert.That(data[0] == 255, "Cached Item element should be 255"); + } + + [Test] + public void TestNotInCache() + { + UUID randomNotIn = UUID.Random(); + while (randomNotIn == cacheItemUUID) + { + randomNotIn = UUID.Random(); + } + object citem = cache.Get(randomNotIn.ToString()); + Assert.That(citem == null, "Item should not be in Cache"); + } + + + [Test] + public void ExpireItemManually() + { + UUID ImmediateExpiryUUID = UUID.Random(); + MemoryCacheItem cachedItem = new MemoryCacheItem(ImmediateExpiryUUID.ToString(), TimeSpan.FromDays(1)); + byte[] foo = new byte[1]; + foo[0] = 1; + cachedItem.Store(foo); + cache.Store(cacheItemUUID.ToString(), cachedItem); + cache.Invalidate(cacheItemUUID.ToString()); + cache.Get(cacheItemUUID.ToString()); + object citem = cache.Get(cacheItemUUID.ToString()); + Assert.That(citem == null, "Item should not be in Cache because we manually invalidated it"); + } + + [Test] + public void ClearCacheTest() + { + UUID ImmediateExpiryUUID = UUID.Random(); + MemoryCacheItem cachedItem = new MemoryCacheItem(ImmediateExpiryUUID.ToString(), DateTime.Now - TimeSpan.FromDays(1)); + byte[] foo = new byte[1]; + foo[0] = 1; + cachedItem.Store(foo); + cache.Store(cacheItemUUID.ToString(), cachedItem); + cache.Clear(); + + object citem = cache.Get(cacheItemUUID.ToString()); + Assert.That(citem == null, "Item should not be in Cache because we manually invalidated it"); + } + + [Test] + public void CacheItemMundane() + { + UUID Random1 = UUID.Random(); + UUID Random2 = UUID.Random(); + byte[] data = Array.Empty(); + CacheItemBase cb1 = new CacheItemBase(Random1.ToString(), DateTime.Now + TimeSpan.FromDays(1)); + CacheItemBase cb2 = new CacheItemBase(Random2.ToString(), DateTime.Now + TimeSpan.FromDays(1)); + CacheItemBase cb3 = new CacheItemBase(Random1.ToString(), DateTime.Now + TimeSpan.FromDays(1)); + + cb1.Store(data); + + Assert.That(cb1.Equals(cb3), "cb1 should equal cb3, their uuids are the same"); + Assert.That(!cb2.Equals(cb1), "cb2 should not equal cb1, their uuids are NOT the same"); + Assert.That(cb1.IsLocked() == false, "CacheItemBase default is false"); + Assert.That(cb1.Retrieve() == null, "Virtual Retrieve method should return null"); + } + } +} diff --git a/Tests/OpenSim.Framework.Tests/LocationTest.cs b/Tests/OpenSim.Framework.Tests/LocationTest.cs new file mode 100644 index 00000000000..9ba52468245 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/LocationTest.cs @@ -0,0 +1,85 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OpenSim.Framework.Tests +{ + public class LocationTest + { + [Test] + public void locationRegionHandleRegionHandle() + { + //1099511628032000 + // 256000 + // 256000 + Location TestLocation1 = new Location(1099511628032000); + Location TestLocation2 = new Location(1099511628032000); + Assert.That(TestLocation1 == TestLocation2); + + TestLocation1 = new Location(1099511628032001); + TestLocation2 = new Location(1099511628032000); + Assert.That(TestLocation1 != TestLocation2); + } + + [Test] + public void locationXYRegionHandle() + { + Location TestLocation1 = new Location(255000,256000); + Location TestLocation2 = new Location(1095216660736000); + Assert.That(TestLocation1 == TestLocation2); + + Assert.That(TestLocation1.X == 255000 && TestLocation1.Y == 256000, "Test xy location doesn't match position in the constructor"); + Assert.That(TestLocation2.X == 255000 && TestLocation2.Y == 256000, "Test xy location doesn't match regionhandle provided"); + + Assert.That(TestLocation2.RegionHandle == 1095216660736000, + "Location RegionHandle Property didn't match regionhandle provided in constructor"); + + ulong RegionHandle = TestLocation1.RegionHandle; + Assert.That(RegionHandle.Equals(1095216660736000), "Equals(regionhandle) failed to match the position in the constructor"); + + TestLocation2 = new Location(RegionHandle); + Assert.That(TestLocation2.Equals(255000, 256000), "Decoded regionhandle failed to match the original position in the constructor"); + + + TestLocation1 = new Location(255001, 256001); + TestLocation2 = new Location(1095216660736000); + Assert.That(TestLocation1 != TestLocation2); + + Assert.That(TestLocation1.Equals(255001, 256001), "Equals(x,y) failed to match the position in the constructor"); + + Assert.That(TestLocation2.GetHashCode() == (TestLocation2.X.GetHashCode() ^ TestLocation2.Y.GetHashCode()), "GetHashCode failed to produce the expected hashcode"); + + Location TestLocation3; + object cln = TestLocation2.Clone(); + TestLocation3 = (Location) cln; + Assert.That(TestLocation3.X == TestLocation2.X && TestLocation3.Y == TestLocation2.Y, + "Cloned Location values do not match"); + + Assert.That(TestLocation2.Equals(cln), "Cloned object failed .Equals(obj) Test"); + } + + } +} diff --git a/Tests/OpenSim.Framework.Tests/MundaneFrameworkTests.cs b/Tests/OpenSim.Framework.Tests/MundaneFrameworkTests.cs new file mode 100644 index 00000000000..d35e10f4337 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/MundaneFrameworkTests.cs @@ -0,0 +1,281 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using System.Globalization; + +namespace OpenSim.Framework.Tests +{ + public class MundaneFrameworkTests + { + private bool m_RegionSettingsOnSaveEventFired; + + [Test] + public void ChildAgentDataUpdate01() + { + // code coverage + ChildAgentDataUpdate cadu = new ChildAgentDataUpdate(); + Assert.That(cadu.alwaysrun is false, "Default is false"); + } + + [Test] + public void AgentPositionTest01() + { + UUID AgentId1 = UUID.Random(); + UUID SessionId1 = UUID.Random(); + uint CircuitCode1 = uint.MinValue; + Vector3 Size1 = Vector3.UnitZ; + Vector3 Position1 = Vector3.UnitX; + Vector3 LeftAxis1 = Vector3.UnitY; + Vector3 UpAxis1 = Vector3.UnitZ; + Vector3 AtAxis1 = Vector3.UnitX; + + ulong RegionHandle1 = ulong.MinValue; + byte[] Throttles1 = new byte[] {0, 1, 0}; + + Vector3 Velocity1 = Vector3.Zero; + float Far1 = 256; + + bool ChangedGrid1 = false; + Vector3 Center1 = Vector3.Zero; + + AgentPosition position1 = new AgentPosition(); + position1.AgentID = AgentId1; + position1.SessionID = SessionId1; + position1.CircuitCode = CircuitCode1; + position1.Size = Size1; + position1.Position = Position1; + position1.LeftAxis = LeftAxis1; + position1.UpAxis = UpAxis1; + position1.AtAxis = AtAxis1; + position1.RegionHandle = RegionHandle1; + position1.Throttles = Throttles1; + position1.Velocity = Velocity1; + position1.Far = Far1; + position1.ChangedGrid = ChangedGrid1; + position1.Center = Center1; + + ChildAgentDataUpdate cadu = new ChildAgentDataUpdate(); + cadu.AgentID = AgentId1.Guid; + cadu.ActiveGroupID = UUID.Zero.Guid; + cadu.throttles = Throttles1; + cadu.drawdistance = Far1; + cadu.Position = Position1; + cadu.Velocity = Velocity1; + cadu.regionHandle = RegionHandle1; + cadu.cameraPosition = Center1; + cadu.AVHeight = Size1.Z; + + AgentPosition position2 = new AgentPosition(); + position2.CopyFrom(cadu, position1.SessionID); + + Assert.That( + position2.AgentID == position1.AgentID + && position2.Size == position1.Size + && position2.Position == position1.Position + && position2.Velocity == position1.Velocity + && position2.Center == position1.Center + && position2.RegionHandle == position1.RegionHandle + && position2.Far == position1.Far + ,"Copy From ChildAgentDataUpdate failed"); + + position2 = new AgentPosition(); + + Assert.That((position2.AgentID == position1.AgentID) is false, "Test Error, position2 should be a blank uninitialized AgentPosition"); + EntityTransferContext ctx = new EntityTransferContext(); + position2.Unpack(position1.Pack(ctx), null, ctx); + + Assert.That(position2.AgentID == position1.AgentID, "Agent ID didn't unpack the same way it packed"); + Assert.That(position2.Position == position1.Position, "Position didn't unpack the same way it packed"); + Assert.That(position2.Velocity == position1.Velocity, "Velocity didn't unpack the same way it packed"); + Assert.That(position2.SessionID == position1.SessionID, "SessionID didn't unpack the same way it packed"); + Assert.That(position2.CircuitCode == position1.CircuitCode, "CircuitCode didn't unpack the same way it packed"); + Assert.That(position2.LeftAxis == position1.LeftAxis, "LeftAxis didn't unpack the same way it packed"); + Assert.That(position2.UpAxis == position1.UpAxis, "UpAxis didn't unpack the same way it packed"); + Assert.That(position2.AtAxis == position1.AtAxis, "AtAxis didn't unpack the same way it packed"); + Assert.That(position2.RegionHandle == position1.RegionHandle, "RegionHandle didn't unpack the same way it packed"); + Assert.That(position2.ChangedGrid == position1.ChangedGrid, "ChangedGrid didn't unpack the same way it packed"); + Assert.That(position2.Center == position1.Center, "Center didn't unpack the same way it packed"); + Assert.That(position2.Size == position1.Size, "Size didn't unpack the same way it packed"); + } + + [Test] + public void RegionSettingsTest01() + { + RegionSettings settings = new RegionSettings(); + settings.OnSave += RegionSaveFired; + settings.Save(); + settings.OnSave -= RegionSaveFired; + +// string str = settings.LoadedCreationDate; +// int dt = settings.LoadedCreationDateTime; +// string id = settings.LoadedCreationID; +// string time = settings.LoadedCreationTime; + + Assert.That(m_RegionSettingsOnSaveEventFired, "RegionSettings Save Event didn't Fire"); + + } + + internal void RegionSaveFired(RegionSettings settings) + { + m_RegionSettingsOnSaveEventFired = true; + } + + [Test] + public void InventoryItemBaseConstructorTest01() + { + InventoryItemBase b1 = new InventoryItemBase(); + Assert.That(b1.ID == UUID.Zero, "void constructor should create an inventory item with ID = UUID.Zero"); + Assert.That(b1.Owner == UUID.Zero, "void constructor should create an inventory item with Owner = UUID.Zero"); + + UUID ItemID = UUID.Random(); + UUID OwnerID = UUID.Random(); + + InventoryItemBase b2 = new InventoryItemBase(ItemID); + Assert.That(b2.ID == ItemID, "ID constructor should create an inventory item with ID = ItemID"); + Assert.That(b2.Owner == UUID.Zero, "ID constructor should create an inventory item with Owner = UUID.Zero"); + + InventoryItemBase b3 = new InventoryItemBase(ItemID,OwnerID); + Assert.That(b3.ID == ItemID, "ID,OwnerID constructor should create an inventory item with ID = ItemID"); + Assert.That(b3.Owner == OwnerID, "ID,OwnerID constructor should create an inventory item with Owner = OwnerID"); + + } + + [Test] + public void AssetMetaDataNonNullContentTypeTest01() + { + AssetMetadata assetMetadata = new AssetMetadata(); + assetMetadata.ContentType = "image/jp2"; + Assert.That(assetMetadata.Type == (sbyte)AssetType.Texture, "Content type should be AssetType.Texture"); + Assert.That(assetMetadata.ContentType == "image/jp2", "Text of content type should be image/jp2"); + UUID rndID = UUID.Random(); + assetMetadata.ID = rndID.ToString(); + Assert.That(assetMetadata.ID.ToLower() == rndID.ToString().ToLower(), "assetMetadata.ID Setter/Getter not Consistent"); + DateTime fixedTime = DateTime.Now; + assetMetadata.CreationDate = fixedTime; + } + + [Test] + public void EstateSettingsMundateTests() + { + EstateSettings es = new EstateSettings(); + es.AddBan(null); + UUID bannedUserId = UUID.Random(); + es.AddBan(new EstateBan() + { BannedHostAddress = string.Empty, + BannedHostIPMask = string.Empty, + BannedHostNameMask = string.Empty, + BannedUserID = bannedUserId} + ); + Assert.That(es.IsBanned(bannedUserId, 32), "User Should be banned but is not."); + Assert.That(es.IsBanned(UUID.Zero, 32) is false, "User Should not be banned but is."); + + es.RemoveBan(bannedUserId); + + Assert.That(es.IsBanned(bannedUserId, 32) is false, "User Should not be banned but is."); + + es.AddEstateManager(UUID.Zero); + + es.AddEstateManager(bannedUserId); + Assert.That(es.IsEstateManagerOrOwner(bannedUserId), "bannedUserId should be EstateManager but isn't."); + + es.RemoveEstateManager(bannedUserId); + Assert.That(es.IsEstateManagerOrOwner(bannedUserId) is false, "bannedUserID is estateManager but shouldn't be"); + + Assert.That(es.HasAccess(bannedUserId) is false, "bannedUserID has access but shouldn't"); + + es.AddEstateUser(bannedUserId); + + Assert.That(es.HasAccess(bannedUserId), "bannedUserID doesn't have access but should"); + es.RemoveEstateUser(bannedUserId); + + es.AddEstateManager(bannedUserId); + + Assert.That(es.HasAccess(bannedUserId), "bannedUserID doesn't have access but should"); + + Assert.That(es.EstateGroups.Length == 0, "No Estate Groups Added.. so the array should be 0 length"); + + es.AddEstateGroup(bannedUserId); + + Assert.That(es.EstateGroups.Length == 1, "1 Estate Groups Added.. so the array should be 1 length"); + + Assert.That(es.EstateGroups[0] == bannedUserId,"User ID should be in EstateGroups"); + + } + + [Test] + public void InventoryFolderBaseConstructorTest01() + { + UUID uuid1 = UUID.Random(); + UUID uuid2 = UUID.Random(); + + InventoryFolderBase fld = new InventoryFolderBase(uuid1); + Assert.That(fld.ID == uuid1, "ID constructor failed to save value in ID field."); + + fld = new InventoryFolderBase(uuid1, uuid2); + Assert.That(fld.ID == uuid1, "ID,Owner constructor failed to save value in ID field."); + Assert.That(fld.Owner == uuid2, "ID,Owner constructor failed to save value in ID field."); + } + + [Test] + public void AsssetBaseConstructorTest01() + { + AssetBase abase = new AssetBase(); + Assert.That(abase.Metadata != null, "void constructor of AssetBase should have created a MetaData element but didn't."); + UUID itemID = UUID.Random(); + UUID creatorID = UUID.Random(); + abase = new AssetBase(itemID.ToString(), "test item", (sbyte) AssetType.Texture, creatorID.ToString()); + + Assert.That(abase.Metadata != null, "string,string,sbyte,string constructor of AssetBase should have created a MetaData element but didn't."); + Assert.That(abase.ID == itemID.ToString(), "string,string,sbyte,string constructor failed to set ID property"); + Assert.That(abase.Metadata.CreatorID == creatorID.ToString(), "string,string,sbyte,string constructor failed to set Creator ID"); + + + abase = new AssetBase(itemID.ToString(), "test item", -1, creatorID.ToString()); + Assert.That(abase.Metadata != null, "string,string,sbyte,string constructor of AssetBase with unknown type should have created a MetaData element but didn't."); + Assert.That(abase.Metadata.Type == -1, "Unknown Type passed to string,string,sbyte,string constructor and was a known type when it came out again"); + + AssetMetadata metts = new AssetMetadata(); + metts.FullID = itemID; + metts.ID = string.Empty; + metts.Name = "test item"; + abase.Metadata = metts; + + Assert.That(abase.ToString() == itemID.ToString(), "ToString is overriden to be fullID.ToString()"); + Assert.That(abase.ID == itemID.ToString(),"ID should be MetaData.FullID.ToString() when string.empty or null is provided to the ID property"); + } + + [Test] + public void CultureSetCultureTest01() + { + CultureInfo ci = new CultureInfo("en-US", false); + Culture.SetCurrentCulture(); + Assert.That(Thread.CurrentThread.CurrentCulture.Name == ci.Name, "SetCurrentCulture failed to set thread culture to en-US"); + + } + } +} diff --git a/Tests/OpenSim.Framework.Tests/OpenSim.Framework.Tests.csproj b/Tests/OpenSim.Framework.Tests/OpenSim.Framework.Tests.csproj new file mode 100644 index 00000000000..b948a872bd6 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/OpenSim.Framework.Tests.csproj @@ -0,0 +1,50 @@ + + + + net8.0 + enable + enable + false + true + + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/Tests/OpenSim.Framework.Tests/PrimeNumberHelperTests.cs b/Tests/OpenSim.Framework.Tests/PrimeNumberHelperTests.cs new file mode 100644 index 00000000000..d199bc7cd67 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/PrimeNumberHelperTests.cs @@ -0,0 +1,135 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +namespace OpenSim.Framework.Tests +{ + public class PrimeNumberHelperTests + { + [Test] + public void TestGetPrime() + { + int prime1 = PrimeNumberHelper.GetPrime(7919); + Assert.That(prime1 == 8419, "Prime Number Get Prime Failed, 7919 is prime"); + Assert.That(PrimeNumberHelper.IsPrime(prime1),"Prime1 should be prime"); + Assert.That(PrimeNumberHelper.IsPrime(7919), "7919 is prime but is falsely failing the prime test"); + prime1 = PrimeNumberHelper.GetPrime(Int32.MaxValue - 1); + Assert.That(prime1 == -1, "prime1 should have been -1 since there are no primes between Int32.MaxValue-1 and Int32.MaxValue"); + + } + + [Test] + public void Test1000SmallPrimeNumbers() + { + int[] primes = { + 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 37, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, + 97, 101, 103, 107, 109, 113, 127, 131, 137, 139, 149, 151, 157, 163, 167, 173, 179, 181, 191 + , 193, 197, 199, 211, 223, 227, 229, 233, 239, 241, 251, 257, 263, 269, 271, 277, 281, 283, + 293, 307, 311, 313, 317, 331, 337, 347, 349, 353, 359, 367, 373, 379, 383, 389, 397, 401, + 409, 419, 421, 431, 433, 439, 443, 449, 457, 461, 463, 467, 479, 487, 491, 499, 503, 509, + 521, 523, 541, 547, 557, 563, 569, 571, 577, 587, 593, 599, 601, 607, 613, 617, 619, 631, + 641, 643, 647, 653, 659, 661, 673, 677, 683, 691, 701, 709, 719, 727, 733, 739, 743, 751, + 757, 761, 769, 773, 787, 797, 809, 811, 821, 823, 827, 829, 839, 853, 857, 859, 863, 877, + 881, 883, 887, 907, 911, 919, 929, 937, 941, 947, 953, 967, 971, 977, 983, 991, 997, 1009, + 1013, 1019, 1021, 1031, 1033, 1039, 1049, 1051, 1061, 1063, 1069, 1087, 1091, 1093, 1097, + 1103, 1109, 1117, 1123, 1129, 1151, 1153, 1163, 1171, 1181, 1187, 1193, 1201, 1213, 1217, + 1223, 1229, 1231, 1237, 1249, 1259, 1277, 1279, 1283, 1289, 1291, 1297, 1301, 1303, 1307, + 1319, 1321, 1327, 1361, 1367, 1373, 1381, 1399, 1409, 1423, 1427, 1429, 1433, 1439, 1447, + 1451, 1453, 1459, 1471, 1481, 1483, 1487, 1489, 1493, 1499, 1511, 1523, 1531, 1543, 1549, + 1553, 1559, 1567, 1571, 1579, 1583, 1597, 1601, 1607, 1609, 1613, 1619, 1621, 1627, 1637, + 1657, 1663, 1667, 1669, 1693, 1697, 1699, 1709, 1721, 1723, 1733, 1741, 1747, 1753, 1759, + 1777, 1783, 1787, 1789, 1801, 1811, 1823, 1831, 1847, 1861, 1867, 1871, 1873, 1877, 1879, + 1889, 1901, 1907, 1913, 1931, 1933, 1949, 1951, 1973, 1979, 1987, 1993, 1997, 1999, 2003, + 2011, 2017, 2027, 2029, 2039, 2053, 2063, 2069, 2081, 2083, 2087, 2089, 2099, 2111, 2113, + 2129, 2131, 2137, 2141, 2143, 2153, 2161, 2179, 2203, 2207, 2213, 2221, 2237, 2239, 2243, + 2251, 2267, 2269, 2273, 2281, 2287, 2293, 2297, 2309, 2311, 2333, 2339, 2341, 2347, 2351, + 2357, 2371, 2377, 2381, 2383, 2389, 2393, 2399, 2411, 2417, 2423, 2437, 2441, 2447, 2459, + 2467, 2473, 2477, 2503, 2521, 2531, 2539, 2543, 2549, 2551, 2557, 2579, 2591, 2593, 2609, + 2617, 2621, 2633, 2647, 2657, 2659, 2663, 2671, 2677, 2683, 2687, 2689, 2693, 2699, 2707, + 2711, 2713, 2719, 2729, 2731, 2741, 2749, 2753, 2767, 2777, 2789, 2791, 2797, 2801, 2803, + 2819, 2833, 2837, 2843, 2851, 2857, 2861, 2879, 2887, 2897, 2903, 2909, 2917, 2927, 2939, + 2953, 2957, 2963, 2969, 2971, 2999, 3001, 3011, 3019, 3023, 3037, 3041, 3049, 3061, 3067, + 3079, 3083, 3089, 3109, 3119, 3121, 3137, 3163, 3167, 3169, 3181, 3187, 3191, 3203, 3209, + 3217, 3221, 3229, 3251, 3253, 3257, 3259, 3271, 3299, 3301, 3307, 3313, 3319, 3323, 3329, + 3331, 3343, 3347, 3359, 3361, 3371, 3373, 3389, 3391, 3407, 3413, 3433, 3449, 3457, 3461, + 3463, 3467, 3469, 3491, 3499, 3511, 3517, 3527, 3529, 3533, 3539, 3541, 3547, 3557, 3559, + 3571, 3581, 3583, 3593, 3607, 3613, 3617, 3623, 3631, 3637, 3643, 3659, 3671, 3673, 3677, + 3691, 3697, 3701, 3709, 3719, 3727, 3733, 3739, 3761, 3767, 3769, 3779, 3793, 3797, 3803, + 3821, 3823, 3833, 3847, 3851, 3853, 3863, 3877, 3881, 3889, 3907, 3911, 3917, 3919, 3923, + 3929, 3931, 3943, 3947, 3967, 3989, 4001, 4003, 4007, 4013, 4019, 4021, 4027, 4049, 4051, + 4057, 4073, 4079, 4091, 4093, 4099, 4111, 4127, 4129, 4133, 4139, 4153, 4157, 4159, 4177, + 4201, 4211, 4217, 4219, 4229, 4231, 4241, 4243, 4253, 4259, 4261, 4271, 4273, 4283, 4289, + 4297, 4327, 4337, 4339, 4349, 4357, 4363, 4373, 4391, 4397, 4409, 4421, 4423, 4441, 4447, + 4451, 4457, 4463, 4481, 4483, 4493, 4507, 4513, 4517, 4519, 4523, 4547, 4549, 4561, 4567, + 4583, 4591, 4597, 4603, 4621, 4637, 4639, 4643, 4649, 4651, 4657, 4663, 4673, 4679, 4691, + 4703, 4721, 4723, 4729, 4733, 4751, 4759, 4783, 4787, 4789, 4793, 4799, 4801, 4813, 4817, + 4831, 4861, 4871, 4877, 4889, 4903, 4909, 4919, 4931, 4933, 4937, 4943, 4951, 4957, 4967, + 4969, 4973, 4987, 4993, 4999, 5003, 5009, 5011, 5021, 5023, 5039, 5051, 5059, 5077, 5081, + 5087, 5099, 5101, 5107, 5113, 5119, 5147, 5153, 5167, 5171, 5179, 5189, 5197, 5209, 5227, + 5231, 5233, 5237, 5261, 5273, 5279, 5281, 5297, 5303, 5309, 5323, 5333, 5347, 5351, 5381, + 5387, 5393, 5399, 5407, 5413, 5417, 5419, 5431, 5437, 5441, 5443, 5449, 5471, 5477, 5479, + 5483, 5501, 5503, 5507, 5519, 5521, 5527, 5531, 5557, 5563, 5569, 5573, 5581, 5591, 5623, + 5639, 5641, 5647, 5651, 5653, 5657, 5659, 5669, 5683, 5689, 5693, 5701, 5711, 5717, 5737, + 5741, 5743, 5749, 5779, 5783, 5791, 5801, 5807, 5813, 5821, 5827, 5839, 5843, 5849, 5851, + 5857, 5861, 5867, 5869, 5879, 5881, 5897, 5903, 5923, 5927, 5939, 5953, 5981, 5987, 6007, + 6011, 6029, 6037, 6043, 6047, 6053, 6067, 6073, 6079, 6089, 6091, 6101, 6113, 6121, 6131, + 6133, 6143, 6151, 6163, 6173, 6197, 6199, 6203, 6211, 6217, 6221, 6229, 6247, 6257, 6263, + 6269, 6271, 6277, 6287, 6299, 6301, 6311, 6317, 6323, 6329, 6337, 6343, 6353, 6359, 6361, + 6367, 6373, 6379, 6389, 6397, 6421, 6427, 6449, 6451, 6469, 6473, 6481, 6491, 6521, 6529, + 6547, 6551, 6553, 6563, 6569, 6571, 6577, 6581, 6599, 6607, 6619, 6637, 6653, 6659, 6661, + 6673, 6679, 6689, 6691, 6701, 6703, 6709, 6719, 6733, 6737, 6761, 6763, 6779, 6781, 6791, + 6793, 6803, 6823, 6827, 6829, 6833, 6841, 6857, 6863, 6869, 6871, 6883, 6899, 6907, 6911, + 6917, 6947, 6949, 6959, 6961, 6967, 6971, 6977, 6983, 6991, 6997, 7001, 7013, 7019, 7027, + 7039, 7043, 7057, 7069, 7079, 7103, 7109, 7121, 7127, 7129, 7151, 7159, 7177, 7187, 7193, + 7207, 7211, 7213, 7219, 7229, 7237, 7243, 7247, 7253, 7283, 7297, 7307, 7309, 7321, 7331, + 7333, 7349, 7351, 7369, 7393, 7411, 7417, 7433, 7451, 7457, 7459, 7477, 7481, 7487, 7489, + 7499, 7507, 7517, 7523, 7529, 7537, 7541, 7547, 7549, 7559, 7561, 7573, 7577, 7583, 7589, + 7591, 7603, 7607, 7621, 7639, 7643, 7649, 7669, 7673, 7681, 7687, 7691, 7699, 7703, 7717, + 7723, 7727, 7741, 7753, 7757, 7759, 7789, 7793, 7817, 7823, 7829, 7841, 7853, 7867, 7873, + 7877, 7879, 7883, 7901, 7907, 7919 + }; + for (int i = 0; i < primes.Length; i++) + { + Assert.That(PrimeNumberHelper.IsPrime(primes[i]),primes[i] + " is prime but is erroniously failing the prime test"); + } + + int[] nonprimes = { + 4, 6, 8, 10, 14, 16, 18, 22, 28, 30, 36, 40, 42, 46, 52, 58, 60, 66, 70, 72, 78, 82, 88, + 96, 366, 372, 378, 382, 388, 396, 400, 408, 418, 420, 430, 432, 438, 442, 448, 456, 460, 462, + 466, 478, 486, 490, 498, 502, 508, 856, 858, 862, 876, 880, 882, 886, 906, 910, 918, 928, 936, + 940, 946, 952, 966, 970, 976, 982, 990, 996, 1008, 1740, 1746, 1752, 1758, 4650, 4656, 4662, + 4672, 4678, 4690, 7740, 7752, 7756, 7758, 7788, 7792, 7816, 7822, 7828, 7840, 7852, 7866, 7872, + 7876, 7878, 7882, 7900, 7906, 7918 + }; + for (int i = 0; i < nonprimes.Length; i++) + { + Assert.That(!PrimeNumberHelper.IsPrime(nonprimes[i]), nonprimes[i] + " is not prime but is erroniously passing the prime test"); + } + + Assert.That(PrimeNumberHelper.IsPrime(3)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Framework.Tests/UtilTest.cs b/Tests/OpenSim.Framework.Tests/UtilTest.cs new file mode 100644 index 00000000000..34d62beb823 --- /dev/null +++ b/Tests/OpenSim.Framework.Tests/UtilTest.cs @@ -0,0 +1,343 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +namespace OpenSim.Framework.Tests +{ + public class UtilTests + { +// [Test] +// public void VectorOperationTests() +// { +// Vector3 v1, v2; +// double expectedDistance; +// double expectedMagnitude; +// double lowPrecisionTolerance = 0.001; + +// //Lets test a simple case of <0,0,0> and <5,5,5> +// { +// v1 = new Vector3(0, 0, 0); +// v2 = new Vector3(5, 5, 5); +// expectedDistance = 8.66; +// Assert.True(Util.GetDistanceTo(v1, v2), +// new DoubleToleranceConstraint(expectedDistance, lowPrecisionTolerance), +// "Calculated distance between two vectors was not within tolerances."); + +// expectedMagnitude = 0; +// Assert.True(Util.GetMagnitude(v1), Is.EqualTo(0), "Magnitude of null vector was not zero."); + +// expectedMagnitude = 8.66; +// Assert.True(Util.GetMagnitude(v2), +// new DoubleToleranceConstraint(expectedMagnitude, lowPrecisionTolerance), +// "Magnitude of vector was incorrect."); +// /* +// TestDelegate d = delegate() { Util.GetNormalizedVector(v1); }; +// bool causesArgumentException = TestHelpers.AssertThisDelegateCausesArgumentException(d); +// Assert.True(causesArgumentException, Is.True, +// "Getting magnitude of null vector did not cause argument exception."); +// */ +// Vector3 expectedNormalizedVector = new Vector3(.577f, .577f, .577f); +// double expectedNormalizedMagnitude = 1; +// Vector3 normalizedVector = Util.GetNormalizedVector(v2); +// Assert.True(normalizedVector, +// new VectorToleranceConstraint(expectedNormalizedVector, lowPrecisionTolerance), +// "Normalized vector generated from vector was not what was expected."); +// Assert.True(Util.GetMagnitude(normalizedVector), +// new DoubleToleranceConstraint(expectedNormalizedMagnitude, lowPrecisionTolerance), +// "Normalized vector generated from vector does not have magnitude of 1."); +// } + +// //Lets test a simple case of <0,0,0> and <0,0,0> +// { +// v1 = new Vector3(0, 0, 0); +// v2 = new Vector3(0, 0, 0); +// expectedDistance = 0; +// Assert.True(Util.GetDistanceTo(v1, v2), +// new DoubleToleranceConstraint(expectedDistance, lowPrecisionTolerance), +// "Calculated distance between two vectors was not within tolerances."); + +// expectedMagnitude = 0; +// Assert.True(Util.GetMagnitude(v1), Is.EqualTo(0), "Magnitude of null vector was not zero."); + +// expectedMagnitude = 0; +// Assert.True(Util.GetMagnitude(v2), +// new DoubleToleranceConstraint(expectedMagnitude, lowPrecisionTolerance), +// "Magnitude of vector was incorrect."); +// /* +// TestDelegate d = delegate() { Util.GetNormalizedVector(v1); }; +// bool causesArgumentException = TestHelpers.AssertThisDelegateCausesArgumentException(d); +// Assert.True(causesArgumentException, Is.True, +// "Getting magnitude of null vector did not cause argument exception."); + +// d = delegate() { Util.GetNormalizedVector(v2); }; +// causesArgumentException = TestHelpers.AssertThisDelegateCausesArgumentException(d); +// Assert.True(causesArgumentException, Is.True, +// "Getting magnitude of null vector did not cause argument exception."); +// */ +// } + +// //Lets test a simple case of <0,0,0> and <-5,-5,-5> +// { +// v1 = new Vector3(0, 0, 0); +// v2 = new Vector3(-5, -5, -5); +// expectedDistance = 8.66; +// Assert.True(Util.GetDistanceTo(v1, v2), +// new DoubleToleranceConstraint(expectedDistance, lowPrecisionTolerance), +// "Calculated distance between two vectors was not within tolerances."); + +// expectedMagnitude = 0; +// Assert.True(Util.GetMagnitude(v1), Is.EqualTo(0), "Magnitude of null vector was not zero."); + +// expectedMagnitude = 8.66; +// Assert.True(Util.GetMagnitude(v2), +// new DoubleToleranceConstraint(expectedMagnitude, lowPrecisionTolerance), +// "Magnitude of vector was incorrect."); +// /* +// TestDelegate d = delegate() { Util.GetNormalizedVector(v1); }; +// bool causesArgumentException = TestHelpers.AssertThisDelegateCausesArgumentException(d); +// Assert.True(causesArgumentException, Is.True, +// "Getting magnitude of null vector did not cause argument exception."); +// */ +// Vector3 expectedNormalizedVector = new Vector3(-.577f, -.577f, -.577f); +// double expectedNormalizedMagnitude = 1; +// Vector3 normalizedVector = Util.GetNormalizedVector(v2); +// Assert.True(normalizedVector, +// new VectorToleranceConstraint(expectedNormalizedVector, lowPrecisionTolerance), +// "Normalized vector generated from vector was not what was expected."); +// Assert.True(Util.GetMagnitude(normalizedVector), +// new DoubleToleranceConstraint(expectedNormalizedMagnitude, lowPrecisionTolerance), +// "Normalized vector generated from vector does not have magnitude of 1."); +// } +// } + + [Test] + public void UUIDTests() + { + Assert.That(Util.isUUID("01234567-89ab-Cdef-0123-456789AbCdEf") is true, + "A correct UUID wasn't recognized."); + Assert.That(Util.isUUID("FOOBAR67-89ab-Cdef-0123-456789AbCdEf") is false, + "UUIDs with non-hex characters are recognized as correct UUIDs."); + Assert.That(Util.isUUID("01234567") is false, + "Too short UUIDs are recognized as correct UUIDs."); + Assert.That(Util.isUUID("01234567-89ab-Cdef-0123-456789AbCdEf0") is false, + "Too long UUIDs are recognized as correct UUIDs."); + Assert.That(Util.isUUID("01234567-89ab-Cdef-0123+456789AbCdEf") is false, + "UUIDs with wrong format are recognized as correct UUIDs."); + } + + [Test] + public void GetHashGuidTests() + { + string string1 = "This is one string"; + string string2 = "This is another"; + + // Two consecutive runs should equal the same + Assert.That(Util.GetHashGuid(string1, "secret1"), Is.EqualTo(Util.GetHashGuid(string1, "secret1"))); + Assert.That(Util.GetHashGuid(string2, "secret1"), Is.EqualTo(Util.GetHashGuid(string2, "secret1"))); + + // Varying data should not eqal the same + Assert.That(Util.GetHashGuid(string2, "secret1"), Is.Not.EqualTo(Util.GetHashGuid(string1, "secret1"))); + + // Varying secrets should not eqal the same + Assert.That(Util.GetHashGuid(string1, "secret2"), Is.Not.EqualTo(Util.GetHashGuid(string1, "secret1"))); + } + + [Test] + public void SLUtilTypeConvertTests() + { + int[] assettypes = new int[]{-1,0,1,2,3,5,6,7,8,10,11,12,13,17,18,19,20,21,22,24,25}; + string[] contenttypes = new string[] + { + "application/octet-stream", + "image/x-j2c", + "audio/ogg", + "application/vnd.ll.callingcard", + "application/vnd.ll.landmark", + "application/vnd.ll.clothing", + "application/vnd.ll.primitive", + "application/vnd.ll.notecard", + "application/vnd.ll.folder", + "application/vnd.ll.lsltext", + "application/vnd.ll.lslbyte", + "image/tga", + "application/vnd.ll.bodypart", + "audio/x-wav", + "image/tga", + "image/jpeg", + "application/vnd.ll.animation", + "application/vnd.ll.gesture", + "application/x-metaverse-simstate", + "application/vnd.ll.link", + "application/vnd.ll.linkfolder", + }; + + for (int i=0;i + /// NPC performance tests + /// + /// + /// Don't rely on the numbers given by these tests - they will vary a lot depending on what is already cached, + /// how much memory is free, etc. In some cases, later larger tests will apparently take less time than smaller + /// earlier tests. + /// + [TestFixture] + public class NPCPerformanceTests : OpenSimTestCase + { + private TestScene scene; + private AvatarFactoryModule afm; + private UserManagementModule umm; + private AttachmentsModule am; + + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.None; + } + + [OneTimeTearDown] + public void TearDown() + { + scene.Close(); + scene = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); + + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten not to worry about such things. + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public void Init() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("NPC"); + config.Configs["NPC"].Set("Enabled", "true"); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + afm = new AvatarFactoryModule(); + umm = new UserManagementModule(); + am = new AttachmentsModule(); + + scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, config, afm, umm, am, new BasicInventoryAccessModule(), new NPCModule()); + } + + [Test] + public void Test_0001_AddRemove100NPCs() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddRemoveNPCs(100); + } + + [Test] + public void Test_0002_AddRemove1000NPCs() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddRemoveNPCs(1000); + } + + [Test] + public void Test_0003_AddRemove2000NPCs() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddRemoveNPCs(2000); + } + + private void TestAddRemoveNPCs(int numberOfNpcs) + { + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); +// ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); + + // 8 is the index of the first baked texture in AvatarAppearance + UUID originalFace8TextureId = TestHelpers.ParseTail(0x10); + Primitive.TextureEntry originalTe = new Primitive.TextureEntry(UUID.Zero); + Primitive.TextureEntryFace originalTef = originalTe.CreateFace(8); + originalTef.TextureID = originalFace8TextureId; + + // We also need to add the texture to the asset service, otherwise the AvatarFactoryModule will tell + // ScenePresence.SendInitialData() to reset our entire appearance. + scene.AssetService.Store(AssetHelpers.CreateNotecardAsset(originalFace8TextureId)); + + afm.SetAppearance(sp, originalTe, null, new WearableCacheItem[0]); + + INPCModule npcModule = scene.RequestModuleInterface(); + + List npcs = new List(); + + long startGcMemory = GC.GetTotalMemory(true); + Stopwatch sw = new Stopwatch(); + sw.Start(); + + for (int i = 0; i < numberOfNpcs; i++) + { + npcs.Add( + npcModule.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, scene, sp.Appearance)); + } + + for (int i = 0; i < numberOfNpcs; i++) + { + Assert.That(npcs[i], Is.Not.Null); + + ScenePresence npc = scene.GetScenePresence(npcs[i]); + Assert.That(npc, Is.Not.Null); + } + + for (int i = 0; i < numberOfNpcs; i++) + { + Assert.That(npcModule.DeleteNPC(npcs[i], scene), Is.True); + ScenePresence npc = scene.GetScenePresence(npcs[i]); + Assert.That(npc, Is.Null); + } + + sw.Stop(); + + long endGcMemory = GC.GetTotalMemory(true); + + Console.WriteLine("Took {0} ms", sw.ElapsedMilliseconds); + Console.WriteLine( + "End {0} MB, Start {1} MB, Diff {2} MB", + endGcMemory / 1024 / 1024, + startGcMemory / 1024 / 1024, + (endGcMemory - startGcMemory) / 1024 / 1024); + } + } +} diff --git a/Tests/OpenSim.Performance.Tests/ObjectPerformanceTests.cs b/Tests/OpenSim.Performance.Tests/ObjectPerformanceTests.cs new file mode 100644 index 00000000000..5c74f9da2e1 --- /dev/null +++ b/Tests/OpenSim.Performance.Tests/ObjectPerformanceTests.cs @@ -0,0 +1,169 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Tests.Performance +{ + /// + /// Object performance tests + /// + /// + /// Don't rely on the numbers given by these tests - they will vary a lot depending on what is already cached, + /// how much memory is free, etc. In some cases, later larger tests will apparently take less time than smaller + /// earlier tests. + /// + [TestFixture] + public class ObjectPerformanceTests : OpenSimTestCase + { + [TearDown] + public void TearDown() + { + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + +// [Test] +// public void Test0000Clean() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// TestAddObjects(200000); +// } + + [Test] + public void Test_0001_10K_1PrimObjects() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddObjects(1, 10000); + } + + [Test] + public void Test_0002_100K_1PrimObjects() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddObjects(1, 100000); + } + + [Test] + public void Test_0003_200K_1PrimObjects() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddObjects(1, 200000); + } + + [Test] + public void Test_0011_100_100PrimObjects() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddObjects(100, 100); + } + + [Test] + public void Test_0012_1K_100PrimObjects() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddObjects(100, 1000); + } + + [Test] + public void Test_0013_2K_100PrimObjects() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestAddObjects(100, 2000); + } + + private void TestAddObjects(int primsInEachObject, int objectsToAdd) + { + UUID ownerId = new UUID("F0000000-0000-0000-0000-000000000000"); + + // Using a local variable for scene, at least on mono 2.6.7, means that it's much more likely to be garbage + // collected when we teardown this test. If it's done in a member variable, even if that is subsequently + // nulled out, the garbage collect can be delayed. + TestScene scene = new SceneHelpers().SetupScene(); + +// Process process = Process.GetCurrentProcess(); +// long startProcessMemory = process.PrivateMemorySize64; + long startGcMemory = GC.GetTotalMemory(true); + DateTime start = DateTime.Now; + + for (int i = 1; i <= objectsToAdd; i++) + { + SceneObjectGroup so = SceneHelpers.CreateSceneObject(primsInEachObject, ownerId, "part_", i); + Assert.That(scene.AddNewSceneObject(so, false), Is.True, string.Format("Object {0} was not created", i)); + } + + TimeSpan elapsed = DateTime.Now - start; +// long processMemoryAlloc = process.PrivateMemorySize64 - startProcessMemory; + long endGcMemory = GC.GetTotalMemory(false); + + for (int i = 1; i <= objectsToAdd; i++) + { + Assert.That( + scene.GetSceneObjectGroup(TestHelpers.ParseTail(i)), + Is.Not.Null, + string.Format("Object {0} could not be retrieved", i)); + } + + // When a scene object is added to a scene, it is placed in the update list for sending to viewers + // (though in this case we have none). When it is deleted, it is not removed from the update which is + // fine since it will later be ignored. + // + // However, that means that we need to manually run an update here to clear out that list so that deleted + // objects will be clean up by the garbage collector before the next stress test is run. + scene.Update(1); + + Console.WriteLine( + "Took {0}ms, {1}MB ({2} - {3}) to create {4} objects each containing {5} prim(s)", + Math.Round(elapsed.TotalMilliseconds), + (endGcMemory - startGcMemory) / 1024 / 1024, + endGcMemory / 1024 / 1024, + startGcMemory / 1024 / 1024, + objectsToAdd, + primsInEachObject); + + scene.Close(); +// scene = null; + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Performance.Tests/OpenSim.Tests.Performance.csproj b/Tests/OpenSim.Performance.Tests/OpenSim.Tests.Performance.csproj new file mode 100644 index 00000000000..0b4060fd695 --- /dev/null +++ b/Tests/OpenSim.Performance.Tests/OpenSim.Tests.Performance.csproj @@ -0,0 +1,58 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + ..\..\bin\XMLRPC.dll + False + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Performance.Tests/ScriptPerformanceTests.cs b/Tests/OpenSim.Performance.Tests/ScriptPerformanceTests.cs new file mode 100644 index 00000000000..fece2e2bc94 --- /dev/null +++ b/Tests/OpenSim.Performance.Tests/ScriptPerformanceTests.cs @@ -0,0 +1,155 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Tests.Common; + +namespace OpenSim.Tests.Performance +{ + /// + /// Script performance tests + /// + /// + /// Don't rely on the numbers given by these tests - they will vary a lot depending on what is already cached, + /// how much memory is free, etc. In some cases, later larger tests will apparently take less time than smaller + /// earlier tests. + /// + [TestFixture] + public class ScriptPerformanceTests : OpenSimTestCase + { + /* + * private TestScene m_scene; + private XEngine m_xEngine; + private AutoResetEvent m_chatEvent = new AutoResetEvent(false); + + private int m_expectedChatMessages; + private List m_osChatMessagesReceived = new List(); + + [SetUp] + public void Init() + { + //AppDomain.CurrentDomain.SetData("APPBASE", Environment.CurrentDirectory + "/bin"); +// Console.WriteLine(AppDomain.CurrentDomain.BaseDirectory); + m_xEngine = new XEngine(); + + // Necessary to stop serialization complaining + WorldCommModule wcModule = new WorldCommModule(); + + IniConfigSource configSource = new IniConfigSource(); + + IConfig startupConfig = configSource.AddConfig("Startup"); + startupConfig.Set("DefaultScriptEngine", "XEngine"); + + IConfig xEngineConfig = configSource.AddConfig("XEngine"); + xEngineConfig.Set("Enabled", "true"); + + // These tests will not run with AppDomainLoading = true, at least on mono. For unknown reasons, the call + // to AssemblyResolver.OnAssemblyResolve fails. + xEngineConfig.Set("AppDomainLoading", "false"); + + m_scene = new SceneHelpers().SetupScene("My Test", UUID.Random(), 1000, 1000, configSource); + SceneHelpers.SetupSceneModules(m_scene, configSource, m_xEngine, wcModule); + + m_scene.EventManager.OnChatFromWorld += OnChatFromWorld; + m_scene.StartScripts(); + } + + [TearDown] + public void TearDown() + { + m_scene.Close(); + m_scene = null; + GC.Collect(); + GC.WaitForPendingFinalizers(); + } + + [Test] + public void TestCompileAndStart100Scripts() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestCompileAndStartScripts(100); + } + + private void TestCompileAndStartScripts(int scriptsToCreate) + { + UUID userId = TestHelpers.ParseTail(0x1); + + m_expectedChatMessages = scriptsToCreate; + int startingObjectIdTail = 0x100; + + GC.Collect(); + + for (int idTail = startingObjectIdTail;idTail < startingObjectIdTail + scriptsToCreate; idTail++) + { + AddObjectAndScript(idTail, userId); + } + + m_chatEvent.WaitOne(40000 + scriptsToCreate * 1000); + + Assert.That(m_osChatMessagesReceived.Count, Is.EqualTo(m_expectedChatMessages)); + + foreach (OSChatMessage msg in m_osChatMessagesReceived) + Assert.That( + msg.Message, + Is.EqualTo("Script running"), + string.Format( + "Message from {0} was {1} rather than {2}", msg.SenderUUID, msg.Message, "Script running")); + } + + private void AddObjectAndScript(int objectIdTail, UUID userId) + { +// UUID itemId = TestHelpers.ParseTail(0x3); + string itemName = string.Format("AddObjectAndScript() Item for object {0}", objectIdTail); + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, "AddObjectAndScriptPart_", objectIdTail); + m_scene.AddNewSceneObject(so, true); + + InventoryItemBase itemTemplate = new InventoryItemBase(); +// itemTemplate.ID = itemId; + itemTemplate.Name = itemName; + itemTemplate.Folder = so.UUID; + itemTemplate.InvType = (int)InventoryType.LSL; + + m_scene.RezNewScript(userId, itemTemplate); + } + + private void OnChatFromWorld(object sender, OSChatMessage oscm) + { +// Console.WriteLine("Got chat [{0}]", oscm.Message); + + lock (m_osChatMessagesReceived) + { + m_osChatMessagesReceived.Add(oscm); + + if (m_osChatMessagesReceived.Count == m_expectedChatMessages) + m_chatEvent.Set(); + } + } + } + */ +} \ No newline at end of file diff --git a/Tests/OpenSim.Permissions.Tests/Common.cs b/Tests/OpenSim.Permissions.Tests/Common.cs new file mode 100644 index 00000000000..d9f0a645b01 --- /dev/null +++ b/Tests/OpenSim.Permissions.Tests/Common.cs @@ -0,0 +1,370 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Region.CoreModules.Avatar.Inventory.Transfer; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using PermissionMask = OpenSim.Framework.PermissionMask; + +namespace OpenSim.Tests.Permissions +{ + [SetUpFixture] + public class Common : OpenSimTestCase + { + public static Common TheInstance; + + public static TestScene TheScene + { + get { return TheInstance.m_Scene; } + } + + public static ScenePresence[] TheAvatars + { + get { return TheInstance.m_Avatars; } + } + + private static string Perms = "Owner: {0}; Group: {1}; Everyone: {2}; Next: {3}"; + private TestScene m_Scene; + private ScenePresence[] m_Avatars = new ScenePresence[3]; + + [SetUp] + public override void SetUp() + { + if (TheInstance == null) + TheInstance = this; + + base.SetUp(); + TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Messaging"); + config.Configs["Messaging"].Set("InventoryTransferModule", "InventoryTransferModule"); + + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + config.AddConfig("InventoryService"); + config.Configs["InventoryService"].Set("LocalServiceModule", "OpenSim.Services.InventoryService.dll:XInventoryService"); + config.Configs["InventoryService"].Set("StorageProvider", "OpenSim.Tests.Common.dll:TestXInventoryDataPlugin"); + + config.AddConfig("Groups"); + config.Configs["Groups"].Set("Enabled", "true"); + config.Configs["Groups"].Set("Module", "Groups Module V2"); + config.Configs["Groups"].Set("StorageProvider", "OpenSim.Tests.Common.dll:TestGroupsDataPlugin"); + config.Configs["Groups"].Set("ServicesConnectorModule", "Groups Local Service Connector"); + config.Configs["Groups"].Set("LocalService", "local"); + + m_Scene = new SceneHelpers().SetupScene("Test", UUID.Random(), 1000, 1000, config); + // Add modules + SceneHelpers.SetupSceneModules(m_Scene, config, new DefaultPermissionsModule(), new InventoryTransferModule(), new BasicInventoryAccessModule()); + + SetUpBasicEnvironment(); + } + + /// + /// The basic environment consists of: + /// - 3 avatars: A1, A2, A3 + /// - 6 simple boxes inworld belonging to A0 and with Next Owner perms: + /// C, CT, MC, MCT, MT, T + /// - Copies of all of these boxes in A0's inventory in the Objects folder + /// - One additional box inworld and in A0's inventory which is a copy of MCT, but + /// with C removed in inventory. This one is called MCT-C + /// + private void SetUpBasicEnvironment() + { + Console.WriteLine("===> SetUpBasicEnvironment <==="); + + // Add 3 avatars + for (int i = 0; i < 3; i++) + { + UUID id = TestHelpers.ParseTail(i + 1); + + m_Avatars[i] = AddScenePresence("Bot", "Bot_" + (i+1), id); + Assert.That(m_Avatars[i], Is.Not.Null); + Assert.That(m_Avatars[i].IsChildAgent, Is.False); + Assert.That(m_Avatars[i].UUID, Is.EqualTo(id)); + Assert.That(m_Scene.GetScenePresences().Count, Is.EqualTo(i + 1)); + } + + AddA1Object("Box C", 10, PermissionMask.Copy); + AddA1Object("Box CT", 11, PermissionMask.Copy | PermissionMask.Transfer); + AddA1Object("Box MC", 12, PermissionMask.Modify | PermissionMask.Copy); + AddA1Object("Box MCT", 13, PermissionMask.Modify | PermissionMask.Copy | PermissionMask.Transfer); + AddA1Object("Box MT", 14, PermissionMask.Modify | PermissionMask.Transfer); + AddA1Object("Box T", 15, PermissionMask.Transfer); + + // MCT-C + AddA1Object("Box MCT-C", 16, PermissionMask.Modify | PermissionMask.Copy | PermissionMask.Transfer); + + Thread.Sleep(5000); + + InventoryFolderBase objsFolder = UserInventoryHelpers.GetInventoryFolder(m_Scene.InventoryService, m_Avatars[0].UUID, "Objects"); + List items = m_Scene.InventoryService.GetFolderItems(m_Avatars[0].UUID, objsFolder.ID); + Assert.That(items.Count, Is.EqualTo(7)); + + RevokePermission(0, "Box MCT-C", PermissionMask.Copy); + } + + private ScenePresence AddScenePresence(string first, string last, UUID id) + { + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(m_Scene, first, last, id, "pw"); + ScenePresence sp = SceneHelpers.AddScenePresence(m_Scene, id); + Assert.That(m_Scene.AuthenticateHandler.GetAgentCircuitData(id), Is.Not.Null); + + return sp; + } + + private void AddA1Object(string name, int suffix, PermissionMask nextOwnerPerms) + { + // Create a Box. Default permissions are just T + SceneObjectGroup box = AddSceneObject(name, suffix, 1, m_Avatars[0].UUID); + Assert.True((box.RootPart.NextOwnerMask & (int)PermissionMask.Copy) == 0); + Assert.True((box.RootPart.NextOwnerMask & (int)PermissionMask.Modify) == 0); + Assert.True((box.RootPart.NextOwnerMask & (int)PermissionMask.Transfer) != 0); + + // field = 16 is NextOwner + // set = 1 means add the permission; set = 0 means remove permission + + if ((nextOwnerPerms & PermissionMask.Copy) != 0) + m_Scene.HandleObjectPermissionsUpdate(m_Avatars[0].ControllingClient, m_Avatars[0].UUID, + m_Avatars[0].ControllingClient.SessionId, 16, box.LocalId, (uint)PermissionMask.Copy, 1); + + if ((nextOwnerPerms & PermissionMask.Modify) != 0) + m_Scene.HandleObjectPermissionsUpdate(m_Avatars[0].ControllingClient, m_Avatars[0].UUID, + m_Avatars[0].ControllingClient.SessionId, 16, box.LocalId, (uint)PermissionMask.Modify, 1); + + if ((nextOwnerPerms & PermissionMask.Transfer) == 0) + m_Scene.HandleObjectPermissionsUpdate(m_Avatars[0].ControllingClient, m_Avatars[0].UUID, + m_Avatars[0].ControllingClient.SessionId, 16, box.LocalId, (uint)PermissionMask.Transfer, 0); + + PrintPerms(box); + AssertPermissions(nextOwnerPerms, (PermissionMask)box.RootPart.NextOwnerMask, box.OwnerID.ToString().Substring(34) + " : " + box.Name); + + TakeCopyToInventory(0, box); + + } + + public void RevokePermission(int ownerIndex, string name, PermissionMask perm) + { + InventoryItemBase item = Common.TheInstance.GetItemFromInventory(m_Avatars[ownerIndex].UUID, "Objects", name); + Assert.That(item, Is.Not.Null); + + // Clone it, so to avoid aliasing -- just like the viewer does. + InventoryItemBase clone = Common.TheInstance.CloneInventoryItem(item); + // Revoke the permission in this copy + clone.NextPermissions &= ~(uint)perm; + Common.TheInstance.AssertPermissions((PermissionMask)clone.NextPermissions & ~perm, + (PermissionMask)clone.NextPermissions, Common.TheInstance.IdStr(clone)); + Assert.That(clone.ID == item.ID); + + // Update properties of the item in inventory. This should affect the original item above. + Common.TheScene.UpdateInventoryItem(m_Avatars[ownerIndex].ControllingClient, UUID.Zero, clone.ID, clone); + + item = Common.TheInstance.GetItemFromInventory(m_Avatars[ownerIndex].UUID, "Objects", name); + Assert.That(item, Is.Not.Null); + Common.TheInstance.PrintPerms(item); + Common.TheInstance.AssertPermissions((PermissionMask)item.NextPermissions & ~perm, + (PermissionMask)item.NextPermissions, Common.TheInstance.IdStr(item)); + + } + + public void PrintPerms(SceneObjectGroup sog) + { + Console.WriteLine("SOG " + sog.Name + " (" + sog.OwnerID.ToString().Substring(34) + "): " + + String.Format(Perms, (PermissionMask)sog.EffectiveOwnerPerms, + (PermissionMask)sog.EffectiveGroupPerms, (PermissionMask)sog.EffectiveEveryOnePerms, (PermissionMask)sog.RootPart.NextOwnerMask)); + + } + + public void PrintPerms(InventoryItemBase item) + { + Console.WriteLine("Inv " + item.Name + " (" + item.Owner.ToString().Substring(34) + "): " + + String.Format(Perms, (PermissionMask)item.BasePermissions, + (PermissionMask)item.GroupPermissions, (PermissionMask)item.EveryOnePermissions, (PermissionMask)item.NextPermissions)); + + } + + public void AssertPermissions(PermissionMask desired, PermissionMask actual, string message) + { + if ((desired & PermissionMask.Copy) != 0) + Assert.True((actual & PermissionMask.Copy) != 0, message); + else + Assert.True((actual & PermissionMask.Copy) == 0, message); + + if ((desired & PermissionMask.Modify) != 0) + Assert.True((actual & PermissionMask.Modify) != 0, message); + else + Assert.True((actual & PermissionMask.Modify) == 0, message); + + if ((desired & PermissionMask.Transfer) != 0) + Assert.True((actual & PermissionMask.Transfer) != 0, message); + else + Assert.True((actual & PermissionMask.Transfer) == 0, message); + + } + + public SceneObjectGroup AddSceneObject(string name, int suffix, int partsToTestCount, UUID ownerID) + { + SceneObjectGroup so = SceneHelpers.CreateSceneObject(partsToTestCount, ownerID, name, suffix); + so.Name = name; + so.Description = name; + + Assert.That(m_Scene.AddNewSceneObject(so, false), Is.True); + SceneObjectGroup retrievedSo = m_Scene.GetSceneObjectGroup(so.UUID); + + // If the parts have the same UUID then we will consider them as one and the same + Assert.That(retrievedSo.PrimCount, Is.EqualTo(partsToTestCount)); + + return so; + } + + public void TakeCopyToInventory(int userIndex, SceneObjectGroup sog) + { + InventoryFolderBase objsFolder = UserInventoryHelpers.GetInventoryFolder(m_Scene.InventoryService, m_Avatars[userIndex].UUID, "Objects"); + Assert.That(objsFolder, Is.Not.Null); + + List localIds = new List(); localIds.Add(sog.LocalId); + // This is an async operation + m_Scene.DeRezObjects(m_Avatars[userIndex].ControllingClient, localIds, m_Avatars[userIndex].UUID, DeRezAction.TakeCopy, objsFolder.ID); + } + + public InventoryItemBase GetItemFromInventory(UUID userID, string folderName, string itemName) + { + InventoryFolderBase objsFolder = UserInventoryHelpers.GetInventoryFolder(m_Scene.InventoryService, userID, folderName); + Assert.That(objsFolder, Is.Not.Null); + List items = m_Scene.InventoryService.GetFolderItems(userID, objsFolder.ID); + InventoryItemBase item = items.Find(i => i.Name == itemName); + Assert.That(item, Is.Not.Null); + + return item; + } + + public InventoryItemBase CloneInventoryItem(InventoryItemBase item) + { + InventoryItemBase clone = new InventoryItemBase(item.ID); + clone.Name = item.Name; + clone.Description = item.Description; + clone.AssetID = item.AssetID; + clone.AssetType = item.AssetType; + clone.BasePermissions = item.BasePermissions; + clone.CreatorId = item.CreatorId; + clone.CurrentPermissions = item.CurrentPermissions; + clone.EveryOnePermissions = item.EveryOnePermissions; + clone.Flags = item.Flags; + clone.Folder = item.Folder; + clone.GroupID = item.GroupID; + clone.GroupOwned = item.GroupOwned; + clone.GroupPermissions = item.GroupPermissions; + clone.InvType = item.InvType; + clone.NextPermissions = item.NextPermissions; + clone.Owner = item.Owner; + + return clone; + } + + public void DeleteObjectsFolders() + { + // Delete everything in A2 and A3's Objects folders, so we can restart + for (int i = 1; i < 3; i++) + { + InventoryFolderBase objsFolder = UserInventoryHelpers.GetInventoryFolder(Common.TheScene.InventoryService, Common.TheAvatars[i].UUID, "Objects"); + Assert.That(objsFolder, Is.Not.Null); + + List items = Common.TheScene.InventoryService.GetFolderItems(Common.TheAvatars[i].UUID, objsFolder.ID); + List ids = new List(); + foreach (InventoryItemBase it in items) + ids.Add(it.ID); + + Common.TheScene.InventoryService.DeleteItems(Common.TheAvatars[i].UUID, ids); + items = Common.TheScene.InventoryService.GetFolderItems(Common.TheAvatars[i].UUID, objsFolder.ID); + Assert.That(items.Count, Is.EqualTo(0), "A" + (i + 1)); + } + + } + + public string IdStr(InventoryItemBase item) + { + return item.Owner.ToString().Substring(34) + " : " + item.Name; + } + + public string IdStr(SceneObjectGroup sog) + { + return sog.OwnerID.ToString().Substring(34) + " : " + sog.Name; + } + + public void GiveInventoryItem(UUID itemId, ScenePresence giverSp, ScenePresence receiverSp) + { + TestClient giverClient = (TestClient)giverSp.ControllingClient; + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + byte[] giveImBinaryBucket = new byte[17]; + byte[] itemIdBytes = itemId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_Scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + GridInstantMessage acceptIm + = new GridInstantMessage( + m_Scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryAccepted, + false, + "inventory accepted msg", + initialSessionId, + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(acceptIm); + } + } +} diff --git a/Tests/OpenSim.Permissions.Tests/DirectTransferTests.cs b/Tests/OpenSim.Permissions.Tests/DirectTransferTests.cs new file mode 100644 index 00000000000..0f251dba436 --- /dev/null +++ b/Tests/OpenSim.Permissions.Tests/DirectTransferTests.cs @@ -0,0 +1,153 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; +using PermissionMask = OpenSim.Framework.PermissionMask; + +namespace OpenSim.Tests.Permissions +{ + /// + /// Basic scene object tests (create, read and delete but not update). + /// + [TestFixture] + public class DirectTransferTests + { + + [SetUp] + public void SetUp() + { + // In case we're dealing with some older version of nunit + if (Common.TheInstance == null) + { + Common.TheInstance = new Common(); + Common.TheInstance.SetUp(); + } + + Common.TheInstance.DeleteObjectsFolders(); + } + + /// + /// Test giving simple objecta with various combinations of next owner perms. + /// + [Test] + public void TestGiveBox() + { + TestHelpers.InMethod(); + + // C, CT, MC, MCT, MT, T + string[] names = new string[6] { "Box C", "Box CT", "Box MC", "Box MCT", "Box MT", "Box T" }; + PermissionMask[] perms = new PermissionMask[6] { + PermissionMask.Copy, + PermissionMask.Copy | PermissionMask.Transfer, + PermissionMask.Modify | PermissionMask.Copy, + PermissionMask.Modify | PermissionMask.Copy | PermissionMask.Transfer, + PermissionMask.Modify | PermissionMask.Transfer, + PermissionMask.Transfer + }; + + for (int i = 0; i < 6; i++) + TestOneBox(names[i], perms[i]); + } + + private void TestOneBox(string name, PermissionMask mask) + { + InventoryItemBase item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[0].UUID, "Objects", name); + + Common.TheInstance.GiveInventoryItem(item.ID, Common.TheAvatars[0], Common.TheAvatars[1]); + + item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[1].UUID, "Objects", name); + + // Check the receiver + Common.TheInstance.PrintPerms(item); + Common.TheInstance.AssertPermissions(mask, (PermissionMask)item.BasePermissions, item.Owner.ToString().Substring(34) + " : " + item.Name); + + int nObjects = Common.TheScene.GetSceneObjectGroups().Count; + // Rez it and check perms in scene too + Common.TheScene.RezObject(Common.TheAvatars[1].ControllingClient, item.ID, UUID.Zero, Vector3.One, Vector3.Zero, UUID.Zero, 0, false, false, false, UUID.Zero); + Assert.That(Common.TheScene.GetSceneObjectGroups().Count, Is.EqualTo(nObjects + 1)); + + SceneObjectGroup box = Common.TheScene.GetSceneObjectGroups().Find(sog => sog.OwnerID == Common.TheAvatars[1].UUID && sog.Name == name); + Common.TheInstance.PrintPerms(box); + Assert.That(box, Is.Not.Null); + + // Check Owner permissions + Common.TheInstance.AssertPermissions(mask, (PermissionMask)box.EffectiveOwnerPerms, box.OwnerID.ToString().Substring(34) + " : " + box.Name); + + // Check Next Owner permissions + Common.TheInstance.AssertPermissions(mask, (PermissionMask)box.RootPart.NextOwnerMask, box.OwnerID.ToString().Substring(34) + " : " + box.Name); + + } + + /// + /// Test giving simple objecta with variour combinations of next owner perms. + /// + [Test] + public void TestDoubleGiveWithChange() + { + TestHelpers.InMethod(); + + string name = "Box MCT-C"; + InventoryItemBase item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[0].UUID, "Objects", name); + + // Now give the item to A2. We give the original item, not a clone. + // The giving methods are supposed to duplicate it. + Common.TheInstance.GiveInventoryItem(item.ID, Common.TheAvatars[0], Common.TheAvatars[1]); + + item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[1].UUID, "Objects", name); + + // Check the receiver + Common.TheInstance.PrintPerms(item); + Common.TheInstance.AssertPermissions(PermissionMask.Modify | PermissionMask.Transfer, + (PermissionMask)item.BasePermissions, Common.TheInstance.IdStr(item)); + + // --------------------------- + // Second transfer + //---------------------------- + + // A2 revokes M + Common.TheInstance.RevokePermission(1, name, PermissionMask.Modify); + + item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[1].UUID, "Objects", name); + + // Now give the item to A3. We give the original item, not a clone. + // The giving methods are supposed to duplicate it. + Common.TheInstance.GiveInventoryItem(item.ID, Common.TheAvatars[1], Common.TheAvatars[2]); + + item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[2].UUID, "Objects", name); + + // Check the receiver + Common.TheInstance.PrintPerms(item); + Common.TheInstance.AssertPermissions(PermissionMask.Transfer, + (PermissionMask)item.BasePermissions, Common.TheInstance.IdStr(item)); + + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Permissions.Tests/IndirectTransferTests.cs b/Tests/OpenSim.Permissions.Tests/IndirectTransferTests.cs new file mode 100644 index 00000000000..909bda31b1d --- /dev/null +++ b/Tests/OpenSim.Permissions.Tests/IndirectTransferTests.cs @@ -0,0 +1,132 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.Threading; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; +using PermissionMask = OpenSim.Framework.PermissionMask; + +namespace OpenSim.Tests.Permissions +{ + /// + /// Basic scene object tests (create, read and delete but not update). + /// + [TestFixture] + public class IndirectTransferTests + { + + [SetUp] + public void SetUp() + { + // In case we're dealing with some older version of nunit + if (Common.TheInstance == null) + { + Common.TheInstance = new Common(); + Common.TheInstance.SetUp(); + } + Common.TheInstance.DeleteObjectsFolders(); + } + + /// + /// Test giving simple objecta with various combinations of next owner perms. + /// + [Test] + public void SimpleTakeCopy() + { + TestHelpers.InMethod(); + + // The Objects folder of A2 + InventoryFolderBase objsFolder = UserInventoryHelpers.GetInventoryFolder(Common.TheScene.InventoryService, Common.TheAvatars[1].UUID, "Objects"); + + // C, CT, MC, MCT, MT, T + string[] names = new string[6] { "Box C", "Box CT", "Box MC", "Box MCT", "Box MT", "Box T" }; + PermissionMask[] perms = new PermissionMask[6] { + PermissionMask.Copy, + PermissionMask.Copy | PermissionMask.Transfer, + PermissionMask.Modify | PermissionMask.Copy, + PermissionMask.Modify | PermissionMask.Copy | PermissionMask.Transfer, + PermissionMask.Modify | PermissionMask.Transfer, + PermissionMask.Transfer + }; + + // Try A2 takes copies of objects that cannot be copied. + for (int i = 0; i < 6; i++) + TakeOneBox(Common.TheScene.GetSceneObjectGroups(), names[i], perms[i]); + // Ad-hoc. Enough time to let the take work. + Thread.Sleep(6000); + + List items = Common.TheScene.InventoryService.GetFolderItems(Common.TheAvatars[1].UUID, objsFolder.ID); + Assert.That(items.Count, Is.EqualTo(0)); + + // A1 makes the objects copyable + for (int i = 0; i < 6; i++) + MakeCopyable(Common.TheScene.GetSceneObjectGroups(), names[i]); + + // Try A2 takes copies of objects that can be copied. + for (int i = 0; i < 6; i++) + TakeOneBox(Common.TheScene.GetSceneObjectGroups(), names[i], perms[i]); + // Ad-hoc. Enough time to let the take work. + Thread.Sleep(6000); + + items = Common.TheScene.InventoryService.GetFolderItems(Common.TheAvatars[1].UUID, objsFolder.ID); + Assert.That(items.Count, Is.EqualTo(6)); + + for (int i = 0; i < 6; i++) + { + InventoryItemBase item = Common.TheInstance.GetItemFromInventory(Common.TheAvatars[1].UUID, "Objects", names[i]); + Assert.That(item, Is.Not.Null); + Common.TheInstance.AssertPermissions(perms[i], (PermissionMask)item.BasePermissions, Common.TheInstance.IdStr(item)); + } + } + + private void TakeOneBox(List objs, string name, PermissionMask mask) + { + // Find the object inworld + SceneObjectGroup box = objs.Find(sog => sog.Name == name && sog.OwnerID == Common.TheAvatars[0].UUID); + Assert.That(box, Is.Not.Null, name); + + // A2's inventory (index 1) + Common.TheInstance.TakeCopyToInventory(1, box); + } + + private void MakeCopyable(List objs, string name) + { + SceneObjectGroup box = objs.Find(sog => sog.Name == name && sog.OwnerID == Common.TheAvatars[0].UUID); + Assert.That(box, Is.Not.Null, name); + + // field = 8 is Everyone + // set = 1 means add the permission; set = 0 means remove permission + Common.TheScene.HandleObjectPermissionsUpdate(Common.TheAvatars[0].ControllingClient, Common.TheAvatars[0].UUID, + Common.TheAvatars[0].ControllingClient.SessionId, 8, box.LocalId, (uint)PermissionMask.Copy, 1); + Common.TheInstance.PrintPerms(box); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Permissions.Tests/OpenSim.Tests.Permissions.csproj b/Tests/OpenSim.Permissions.Tests/OpenSim.Tests.Permissions.csproj new file mode 100644 index 00000000000..98622c7c5df --- /dev/null +++ b/Tests/OpenSim.Permissions.Tests/OpenSim.Tests.Permissions.csproj @@ -0,0 +1,46 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/EventQueue/Tests/EventQueueTests.cs b/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/EventQueue/Tests/EventQueueTests.cs new file mode 100644 index 00000000000..70079c99ea4 --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/EventQueue/Tests/EventQueueTests.cs @@ -0,0 +1,196 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections; +using System.Net; +using System.Text; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.OptionalModules.World.NPC; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ClientStack.Linden.Tests +{ + [TestFixture] + public class EventQueueTests : OpenSimTestCase + { + private TestScene m_scene; + private EventQueueGetModule m_eqgMod; + private NPCModule m_npcMod; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + uint port = 9999; + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, "","",""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Startup"); + + CapabilitiesModule capsModule = new CapabilitiesModule(); + m_eqgMod = new EventQueueGetModule(); + + // For NPC test support + config.AddConfig("NPC"); + config.Configs["NPC"].Set("Enabled", "true"); + m_npcMod = new NPCModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, config, capsModule, m_eqgMod, m_npcMod); + } + + [Test] + public void TestAddForClient() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + + // TODO: Add more assertions for the other aspects of event queues + Assert.That(MainServer.Instance.GetPollServiceHandlerKeys().Count, Is.EqualTo(1)); + } + + [Test] + public void TestRemoveForClient() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spId = TestHelpers.ParseTail(0x1); + + SceneHelpers.AddScenePresence(m_scene, spId); + m_scene.CloseAgent(spId, false); + + // TODO: Add more assertions for the other aspects of event queues + Assert.That(MainServer.Instance.GetPollServiceHandlerKeys().Count, Is.EqualTo(0)); + } + + [Test] + public void TestEnqueueMessage() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + + string messageName = "TestMessage"; + + m_eqgMod.Enqueue(m_eqgMod.BuildEvent(messageName, new OSDMap()), sp.UUID); + + Hashtable eventsResponse = m_eqgMod.GetEvents(UUID.Zero, sp.UUID); + + // initial queue as null events +// eventsResponse = m_eqgMod.GetEvents(UUID.Zero, sp.UUID); + if((int)eventsResponse["int_response_code"] != (int)HttpStatusCode.OK) + { + eventsResponse = m_eqgMod.GetEvents(UUID.Zero, sp.UUID); + if((int)eventsResponse["int_response_code"] != (int)HttpStatusCode.OK) + eventsResponse = m_eqgMod.GetEvents(UUID.Zero, sp.UUID); + } + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.OK)); + +// Console.WriteLine("Response [{0}]", (string)eventsResponse["str_response_string"]); + string data = String.Empty; + if(eventsResponse["bin_response_data"] != null) + data = Encoding.UTF8.GetString((byte[])eventsResponse["bin_response_data"]); + + OSDMap rawOsd = (OSDMap)OSDParser.DeserializeLLSDXml(data); + OSDArray eventsOsd = (OSDArray)rawOsd["events"]; + + bool foundUpdate = false; + foreach (OSD osd in eventsOsd) + { + OSDMap eventOsd = (OSDMap)osd; + + if (eventOsd["message"] == messageName) + foundUpdate = true; + } + + Assert.That(foundUpdate, Is.True, string.Format("Did not find {0} in response", messageName)); + } + + /// + /// Test an attempt to put a message on the queue of a user that is not in the region. + /// + [Test] + public void TestEnqueueMessageNoUser() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + string messageName = "TestMessage"; + + m_eqgMod.Enqueue(m_eqgMod.BuildEvent(messageName, new OSDMap()), TestHelpers.ParseTail(0x1)); + + Hashtable eventsResponse = m_eqgMod.GetEvents(UUID.Zero, TestHelpers.ParseTail(0x1)); + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.NotFound)); + } + + /// + /// NPCs do not currently have an event queue but a caller may try to send a message anyway, so check behaviour. + /// + [Test] + public void TestEnqueueMessageToNpc() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID npcId + = m_npcMod.CreateNPC( + "John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, new AvatarAppearance()); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + string messageName = "TestMessage"; + + m_eqgMod.Enqueue(m_eqgMod.BuildEvent(messageName, new OSDMap()), npc.UUID); + + Hashtable eventsResponse = m_eqgMod.GetEvents(UUID.Zero, npc.UUID); + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.NotFound)); + } + } +} diff --git a/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/OpenSim.Region.ClientStack.LindenCaps.Tests.csproj b/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/OpenSim.Region.ClientStack.LindenCaps.Tests.csproj new file mode 100644 index 00000000000..d02db759732 --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/OpenSim.Region.ClientStack.LindenCaps.Tests.csproj @@ -0,0 +1,57 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/Tests/WebFetchInvDescModuleTests.cs b/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/Tests/WebFetchInvDescModuleTests.cs new file mode 100644 index 00000000000..cd330528268 --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenCaps.Tests/Tests/WebFetchInvDescModuleTests.cs @@ -0,0 +1,161 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; + +using OSHttpServer; + +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Framework.Capabilities; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using OSDArray = OpenMetaverse.StructuredData.OSDArray; +using OSDMap = OpenMetaverse.StructuredData.OSDMap; + +namespace OpenSim.Region.ClientStack.Linden.Caps.Tests +{ + /* + [TestFixture] + public class WebFetchInvDescModuleTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void TestFixtureSetUp() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TestFixureTearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + uint port = 9999; + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, 0, ""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + + server.Start(false); + } + + [Test] + public void TestInventoryDescendentsFetch() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + BaseHttpServer httpServer = MainServer.Instance; + Scene scene = new SceneHelpers().SetupScene(); + + CapabilitiesModule capsModule = new CapabilitiesModule(); + WebFetchInvDescModule wfidModule = new WebFetchInvDescModule(false); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("ClientStack.LindenCaps"); + config.Configs["ClientStack.LindenCaps"].Set("Cap_FetchInventoryDescendents2", "localhost"); + + SceneHelpers.SetupSceneModules(scene, config, capsModule, wfidModule); + + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + + // We need a user present to have any capabilities set up + SceneHelpers.AddScenePresence(scene, ua.PrincipalID); + + TestHttpRequest req = new TestHttpRequest(); + OpenSim.Framework.Capabilities.Caps userCaps = capsModule.GetCapsForUser(ua.PrincipalID); + PollServiceEventArgs pseArgs; + userCaps.TryGetPollHandler("FetchInventoryDescendents2", out pseArgs); + req.UriPath = pseArgs.Url; + req.Uri = new Uri("file://" + req.UriPath); + + // Retrieve root folder details directly so that we can request + InventoryFolderBase folder = scene.InventoryService.GetRootFolder(ua.PrincipalID); + + OSDMap osdFolder = new OSDMap(); + osdFolder["folder_id"] = folder.ID; + osdFolder["owner_id"] = ua.PrincipalID; + osdFolder["fetch_folders"] = true; + osdFolder["fetch_items"] = true; + osdFolder["sort_order"] = 0; + + OSDArray osdFoldersArray = new OSDArray(); + osdFoldersArray.Add(osdFolder); + + OSDMap osdReqMap = new OSDMap(); + osdReqMap["folders"] = osdFoldersArray; + + req.Body = new MemoryStream(OSDParser.SerializeLLSDXmlBytes(osdReqMap)); + + TestHttpClientContext context = new TestHttpClientContext(false); + + // WARNING: This results in a caught exception, because queryString is null + MainServer.Instance.OnRequest(context, new RequestEventArgs(req)); + + // Drive processing of the queued inventory request synchronously. + wfidModule.WaitProcessQueuedInventoryRequest(); + MainServer.Instance.PollServiceRequestManager.WaitPerformResponse(); + +// System.Threading.Thread.Sleep(10000); + + OSDMap responseOsd = (OSDMap)OSDParser.DeserializeLLSDXml(context.ResponseBody); + OSDArray foldersOsd = (OSDArray)responseOsd["folders"]; + OSDMap folderOsd = (OSDMap)foldersOsd[0]; + + // A sanity check that the response has the expected number of descendents for a default inventory + // TODO: Need a more thorough check. + Assert.That((int)folderOsd["descendents"], Is.EqualTo(16)); + } + } + */ +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/BasicCircuitTests.cs b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/BasicCircuitTests.cs new file mode 100644 index 00000000000..36204c6a388 --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/BasicCircuitTests.cs @@ -0,0 +1,266 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Framework.Monitoring; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ClientStack.LindenUDP.Tests +{ + /// + /// This will contain basic tests for the LindenUDP client stack + /// + [TestFixture] + public class BasicCircuitTests : OpenSimTestCase + { + private Scene m_scene; + + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + m_scene = new SceneHelpers().SetupScene(); + StatsManager.SimExtraStats = new SimExtraStatsCollector(); + } + +// /// +// /// Build an object name packet for test purposes +// /// +// /// +// /// +// private ObjectNamePacket BuildTestObjectNamePacket(uint objectLocalId, string objectName) +// { +// ObjectNamePacket onp = new ObjectNamePacket(); +// ObjectNamePacket.ObjectDataBlock odb = new ObjectNamePacket.ObjectDataBlock(); +// odb.LocalID = objectLocalId; +// odb.Name = Utils.StringToBytes(objectName); +// onp.ObjectData = new ObjectNamePacket.ObjectDataBlock[] { odb }; +// onp.Header.Zerocoded = false; +// +// return onp; +// } +// + /// + /// Test adding a client to the stack + /// +/* + [Test] + public void TestAddClient() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(m_scene); + + UUID myAgentUuid = TestHelpers.ParseTail(0x1); + UUID mySessionUuid = TestHelpers.ParseTail(0x2); + uint myCircuitCode = 123456; + IPEndPoint testEp = new IPEndPoint(IPAddress.Loopback, 999); + + UseCircuitCodePacket uccp = new UseCircuitCodePacket(); + + UseCircuitCodePacket.CircuitCodeBlock uccpCcBlock + = new UseCircuitCodePacket.CircuitCodeBlock(); + uccpCcBlock.Code = myCircuitCode; + uccpCcBlock.ID = myAgentUuid; + uccpCcBlock.SessionID = mySessionUuid; + uccp.CircuitCode = uccpCcBlock; + + byte[] uccpBytes = uccp.ToBytes(); + UDPPacketBuffer upb = new UDPPacketBuffer(testEp, uccpBytes.Length); + upb.DataLength = uccpBytes.Length; // God knows why this isn't set by the constructor. + Buffer.BlockCopy(uccpBytes, 0, upb.Data, 0, uccpBytes.Length); + + udpServer.PacketReceived(upb); + + // Presence shouldn't exist since the circuit manager doesn't know about this circuit for authentication yet + Assert.That(m_scene.GetScenePresence(myAgentUuid), Is.Null); + + AgentCircuitData acd = new AgentCircuitData(); + acd.AgentID = myAgentUuid; + acd.SessionID = mySessionUuid; + + m_scene.AuthenticateHandler.AddNewCircuit(myCircuitCode, acd); + + udpServer.PacketReceived(upb); + + // Should succeed now + ScenePresence sp = m_scene.GetScenePresence(myAgentUuid); + Assert.That(sp.UUID, Is.EqualTo(myAgentUuid)); + + Assert.That(udpServer.PacketsSent.Count, Is.EqualTo(1)); + + Packet packet = udpServer.PacketsSent[0]; + Assert.That(packet, Is.InstanceOf(typeof(PacketAckPacket))); + + PacketAckPacket ackPacket = packet as PacketAckPacket; + Assert.That(ackPacket.Packets.Length, Is.EqualTo(1)); + Assert.That(ackPacket.Packets[0].ID, Is.EqualTo(0)); + } + + [Test] + public void TestLogoutClientDueToAck() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IniConfigSource ics = new IniConfigSource(); + IConfig config = ics.AddConfig("ClientStack.LindenUDP"); + config.Set("AckTimeout", -1); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(m_scene, ics); + + ScenePresence sp + = ClientStackHelpers.AddChildClient( + m_scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + udpServer.ClientOutgoingPacketHandler(sp.ControllingClient, true, false, false); + + ScenePresence spAfterAckTimeout = m_scene.GetScenePresence(sp.UUID); + Assert.That(spAfterAckTimeout, Is.Null); + } +*/ +// /// +// /// Test removing a client from the stack +// /// +// [Test] +// public void TestRemoveClient() +// { +// TestHelper.InMethod(); +// +// uint myCircuitCode = 123457; +// +// TestLLUDPServer testLLUDPServer; +// TestLLPacketServer testLLPacketServer; +// AgentCircuitManager acm; +// SetupStack(new MockScene(), out testLLUDPServer, out testLLPacketServer, out acm); +// AddClient(myCircuitCode, new IPEndPoint(IPAddress.Loopback, 1000), testLLUDPServer, acm); +// +// testLLUDPServer.RemoveClientCircuit(myCircuitCode); +// Assert.IsFalse(testLLUDPServer.HasCircuit(myCircuitCode)); +// +// // Check that removing a non-existent circuit doesn't have any bad effects +// testLLUDPServer.RemoveClientCircuit(101); +// Assert.IsFalse(testLLUDPServer.HasCircuit(101)); +// } +// +// /// +// /// Make sure that the client stack reacts okay to malformed packets +// /// +// [Test] +// public void TestMalformedPacketSend() +// { +// TestHelper.InMethod(); +// +// uint myCircuitCode = 123458; +// EndPoint testEp = new IPEndPoint(IPAddress.Loopback, 1001); +// MockScene scene = new MockScene(); +// +// TestLLUDPServer testLLUDPServer; +// TestLLPacketServer testLLPacketServer; +// AgentCircuitManager acm; +// SetupStack(scene, out testLLUDPServer, out testLLPacketServer, out acm); +// AddClient(myCircuitCode, testEp, testLLUDPServer, acm); +// +// byte[] data = new byte[] { 0x01, 0x02, 0x03, 0x04 }; +// +// // Send two garbled 'packets' in succession +// testLLUDPServer.LoadReceive(data, testEp); +// testLLUDPServer.LoadReceive(data, testEp); +// testLLUDPServer.ReceiveData(null); +// +// // Check that we are still here +// Assert.IsTrue(testLLUDPServer.HasCircuit(myCircuitCode)); +// Assert.That(testLLPacketServer.GetTotalPacketsReceived(), Is.EqualTo(0)); +// +// // Check that sending a valid packet to same circuit still succeeds +// Assert.That(scene.ObjectNameCallsReceived, Is.EqualTo(0)); +// +// testLLUDPServer.LoadReceive(BuildTestObjectNamePacket(1, "helloooo"), testEp); +// testLLUDPServer.ReceiveData(null); +// +// Assert.That(testLLPacketServer.GetTotalPacketsReceived(), Is.EqualTo(1)); +// Assert.That(testLLPacketServer.GetPacketsReceivedFor(PacketType.ObjectName), Is.EqualTo(1)); +// } +// +// /// +// /// Test that the stack continues to work even if some client has caused a +// /// SocketException on Socket.BeginReceive() +// /// +// [Test] +// public void TestExceptionOnBeginReceive() +// { +// TestHelper.InMethod(); +// +// MockScene scene = new MockScene(); +// +// uint circuitCodeA = 130000; +// EndPoint epA = new IPEndPoint(IPAddress.Loopback, 1300); +// UUID agentIdA = UUID.Parse("00000000-0000-0000-0000-000000001300"); +// UUID sessionIdA = UUID.Parse("00000000-0000-0000-0000-000000002300"); +// +// uint circuitCodeB = 130001; +// EndPoint epB = new IPEndPoint(IPAddress.Loopback, 1301); +// UUID agentIdB = UUID.Parse("00000000-0000-0000-0000-000000001301"); +// UUID sessionIdB = UUID.Parse("00000000-0000-0000-0000-000000002301"); +// +// TestLLUDPServer testLLUDPServer; +// TestLLPacketServer testLLPacketServer; +// AgentCircuitManager acm; +// SetupStack(scene, out testLLUDPServer, out testLLPacketServer, out acm); +// AddClient(circuitCodeA, epA, agentIdA, sessionIdA, testLLUDPServer, acm); +// AddClient(circuitCodeB, epB, agentIdB, sessionIdB, testLLUDPServer, acm); +// +// testLLUDPServer.LoadReceive(BuildTestObjectNamePacket(1, "packet1"), epA); +// testLLUDPServer.LoadReceive(BuildTestObjectNamePacket(1, "packet2"), epB); +// testLLUDPServer.LoadReceiveWithBeginException(epA); +// testLLUDPServer.LoadReceive(BuildTestObjectNamePacket(2, "packet3"), epB); +// testLLUDPServer.ReceiveData(null); +// +// Assert.IsFalse(testLLUDPServer.HasCircuit(circuitCodeA)); +// +// Assert.That(testLLPacketServer.GetTotalPacketsReceived(), Is.EqualTo(3)); +// Assert.That(testLLPacketServer.GetPacketsReceivedFor(PacketType.ObjectName), Is.EqualTo(3)); +// } + } +} diff --git a/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/LLImageManagerTests.cs b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/LLImageManagerTests.cs new file mode 100644 index 00000000000..788575181ba --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/LLImageManagerTests.cs @@ -0,0 +1,166 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Agent.TextureSender; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ClientStack.LindenUDP.Tests +{ + [TestFixture] + public class LLImageManagerTests : OpenSimTestCase + { + private AssetBase m_testImageAsset; + private Scene scene; + private LLImageManager llim; + private TestClient tc; + + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.None; + + using ( + Stream resource + = GetType().Assembly.GetManifestResourceStream( + "OpenSim.Region.ClientStack.LindenUDP.Tests.Resources.4-tile2.jp2")) + { + using (BinaryReader br = new BinaryReader(resource)) + { + m_testImageAsset + = new AssetBase( + TestHelpers.ParseTail(0x1), + "Test Image", + (sbyte)AssetType.Texture, + TestHelpers.ParseTail(0x2).ToString()); + + m_testImageAsset.Data = br.ReadBytes(99999999); + } + } + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten not to worry about such things. + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + UUID userId = TestHelpers.ParseTail(0x3); + + J2KDecoderModule j2kdm = new J2KDecoderModule(); + + SceneHelpers sceneHelpers = new SceneHelpers(); + scene = sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(scene, j2kdm); + + tc = new TestClient(SceneHelpers.GenerateAgentData(userId), scene); + llim = new LLImageManager(tc, scene.AssetService, j2kdm); + } + + [Test] + public void TestSendImage() + { + TestHelpers.InMethod(); +// XmlConfigurator.Configure(); + + scene.AssetService.Store(m_testImageAsset); + + TextureRequestArgs args = new TextureRequestArgs(); + args.RequestedAssetID = m_testImageAsset.FullID; + args.DiscardLevel = 0; + args.PacketNumber = 1; + args.Priority = 5; + args.requestSequence = 1; + + llim.EnqueueReq(args); + llim.ProcessImageQueue(20); + + Assert.That(tc.SentImageDataPackets.Count, Is.EqualTo(1)); + } + + [Test] + public void TestDiscardImage() + { + TestHelpers.InMethod(); +// XmlConfigurator.Configure(); + + scene.AssetService.Store(m_testImageAsset); + + TextureRequestArgs args = new TextureRequestArgs(); + args.RequestedAssetID = m_testImageAsset.FullID; + args.DiscardLevel = 0; + args.PacketNumber = 1; + args.Priority = 5; + args.requestSequence = 1; + llim.EnqueueReq(args); + + // Now create a discard request + TextureRequestArgs discardArgs = new TextureRequestArgs(); + discardArgs.RequestedAssetID = m_testImageAsset.FullID; + discardArgs.DiscardLevel = -1; + discardArgs.PacketNumber = 1; + discardArgs.Priority = 0; + discardArgs.requestSequence = 2; + llim.EnqueueReq(discardArgs); + + llim.ProcessImageQueue(20); + + Assert.That(tc.SentImageDataPackets.Count, Is.EqualTo(0)); + } + + [Test] + public void TestMissingImage() + { + TestHelpers.InMethod(); +// XmlConfigurator.Configure(); + + TextureRequestArgs args = new TextureRequestArgs(); + args.RequestedAssetID = m_testImageAsset.FullID; + args.DiscardLevel = 0; + args.PacketNumber = 1; + args.Priority = 5; + args.requestSequence = 1; + + llim.EnqueueReq(args); + llim.ProcessImageQueue(20); + + Assert.That(tc.SentImageDataPackets.Count, Is.EqualTo(0)); + Assert.That(tc.SentImageNotInDatabasePackets.Count, Is.EqualTo(1)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/OpenSim.Region.ClientStack.LindenUDP.Tests.csproj b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/OpenSim.Region.ClientStack.LindenUDP.Tests.csproj new file mode 100644 index 00000000000..510db6766b5 --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/OpenSim.Region.ClientStack.LindenUDP.Tests.csproj @@ -0,0 +1,50 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/PacketHandlerTests.cs b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/PacketHandlerTests.cs new file mode 100644 index 00000000000..8b3988b6a22 --- /dev/null +++ b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/PacketHandlerTests.cs @@ -0,0 +1,99 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ClientStack.LindenUDP.Tests +{ + /// + /// Tests for the LL packet handler + /// + [TestFixture] + public class PacketHandlerTests : OpenSimTestCase + { +// [Test] +// /// +// /// More a placeholder, really +// /// +// public void InPacketTest() +// { +// TestHelper.InMethod(); +// +// AgentCircuitData agent = new AgentCircuitData(); +// agent.AgentID = UUID.Random(); +// agent.firstname = "testfirstname"; +// agent.lastname = "testlastname"; +// agent.SessionID = UUID.Zero; +// agent.SecureSessionID = UUID.Zero; +// agent.circuitcode = 123; +// agent.BaseFolder = UUID.Zero; +// agent.InventoryFolder = UUID.Zero; +// agent.startpos = Vector3.Zero; +// agent.CapsPath = "http://wibble.com"; +// +// TestLLUDPServer testLLUDPServer; +// TestLLPacketServer testLLPacketServer; +// AgentCircuitManager acm; +// IScene scene = new MockScene(); +// SetupStack(scene, out testLLUDPServer, out testLLPacketServer, out acm); +// +// TestClient testClient = new TestClient(agent, scene); +// +// LLPacketHandler packetHandler +// = new LLPacketHandler(testClient, testLLPacketServer, new ClientStackUserSettings()); +// +// packetHandler.InPacket(new AgentAnimationPacket()); +// LLQueItem receivedPacket = packetHandler.PacketQueue.Dequeue(); +// +// Assert.That(receivedPacket, Is.Not.Null); +// Assert.That(receivedPacket.Incoming, Is.True); +// Assert.That(receivedPacket.Packet, Is.TypeOf(typeof(AgentAnimationPacket))); +// } +// +// /// +// /// Add a client for testing +// /// +// /// +// /// +// /// +// /// Agent circuit manager used in setting up the stack +// protected void SetupStack( +// IScene scene, out TestLLUDPServer testLLUDPServer, out TestLLPacketServer testPacketServer, +// out AgentCircuitManager acm) +// { +// IConfigSource configSource = new IniConfigSource(); +// ClientStackUserSettings userSettings = new ClientStackUserSettings(); +// testLLUDPServer = new TestLLUDPServer(); +// acm = new AgentCircuitManager(); +// +// uint port = 666; +// testLLUDPServer.Initialise(null, ref port, 0, false, configSource, acm); +// testPacketServer = new TestLLPacketServer(testLLUDPServer, userSettings); +// testLLUDPServer.LocalScene = scene; +// } + } +} diff --git a/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/Resources/4-tile2.jp2 b/Tests/OpenSim.Region.ClientStack.LindenUDP.Tests/Resources/4-tile2.jp2 new file mode 100644 index 0000000000000000000000000000000000000000..8c631046e877a1532c045abc845ab23fb9f78557 GIT binary patch literal 24410 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 h=nDeX#3 + /// Test throttle setttings where max client throttle has been limited server side. + /// + [Test] + public void TestSingleClientThrottleRegionLimited() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + int resendBytes = 6000; + int landBytes = 8000; + int windBytes = 10000; + int cloudBytes = 12000; + int taskBytes = 14000; + int textureBytes = 16000; + int assetBytes = 18000; + int totalBytes + = (int)((resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes) / 2); + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + udpServer.Throttle.RequestedDripRate = totalBytes; + + ScenePresence sp1 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient1 = ((LLClientView)sp1.ControllingClient).UDPClient; + + SetThrottles( + udpClient1, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes / 2, landBytes / 2, windBytes / 2, cloudBytes / 2, taskBytes / 2, + textureBytes / 2, assetBytes / 2, totalBytes, 0, 0); + + // Test: Now add another client + ScenePresence sp2 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x10), TestHelpers.ParseTail(0x20), 123457); + + LLUDPClient udpClient2 = ((LLClientView)sp2.ControllingClient).UDPClient; + // udpClient.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient2, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes / 4, landBytes / 4, windBytes / 4, cloudBytes / 4, taskBytes / 4, + textureBytes / 4, assetBytes / 4, totalBytes / 2, 0, 0); + + AssertThrottles( + udpClient2, + resendBytes / 4, landBytes / 4, windBytes / 4, cloudBytes / 4, taskBytes / 4, + textureBytes / 4, assetBytes / 4, totalBytes / 2, 0, 0); + } + + /// + /// Test throttle setttings where max client throttle has been limited server side. + /// + [Test] + public void TestClientThrottlePerClientLimited() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + int resendBytes = 4000; + int landBytes = 6000; + int windBytes = 8000; + int cloudBytes = 10000; + int taskBytes = 12000; + int textureBytes = 14000; + int assetBytes = 16000; + int totalBytes + = (int)((resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes) / 2); + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + udpServer.ThrottleRates.Total = totalBytes; + + ScenePresence sp + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient = ((LLClientView)sp.ControllingClient).UDPClient; + // udpClient.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient, + resendBytes / 2, landBytes / 2, windBytes / 2, cloudBytes / 2, taskBytes / 2, + textureBytes / 2, assetBytes / 2, totalBytes, 0, totalBytes); + } + + [Test] + public void TestClientThrottlePerClientAndRegionLimited() + { + TestHelpers.InMethod(); + //TestHelpers.EnableLogging(); + + int resendBytes = 4000; + int landBytes = 6000; + int windBytes = 8000; + int cloudBytes = 10000; + int taskBytes = 12000; + int textureBytes = 14000; + int assetBytes = 16000; + + // current total 70000 + int totalBytes = resendBytes + landBytes + windBytes + cloudBytes + taskBytes + textureBytes + assetBytes; + + Scene scene = new SceneHelpers().SetupScene(); + TestLLUDPServer udpServer = ClientStackHelpers.AddUdpServer(scene); + udpServer.ThrottleRates.Total = (int)(totalBytes * 1.1); + udpServer.Throttle.RequestedDripRate = (int)(totalBytes * 1.5); + + ScenePresence sp1 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x1), TestHelpers.ParseTail(0x2), 123456); + + LLUDPClient udpClient1 = ((LLClientView)sp1.ControllingClient).UDPClient; + udpClient1.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient1, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes, landBytes, windBytes, cloudBytes, taskBytes, + textureBytes, assetBytes, totalBytes, 0, totalBytes * 1.1); + + // Now add another client + ScenePresence sp2 + = ClientStackHelpers.AddChildClient( + scene, udpServer, TestHelpers.ParseTail(0x10), TestHelpers.ParseTail(0x20), 123457); + + LLUDPClient udpClient2 = ((LLClientView)sp2.ControllingClient).UDPClient; + udpClient2.ThrottleDebugLevel = 1; + + SetThrottles( + udpClient2, resendBytes, landBytes, windBytes, cloudBytes, taskBytes, textureBytes, assetBytes); + + AssertThrottles( + udpClient1, + resendBytes * 0.75, landBytes * 0.75, windBytes * 0.75, cloudBytes * 0.75, taskBytes * 0.75, + textureBytes * 0.75, assetBytes * 0.75, totalBytes * 0.75, 0, totalBytes * 1.1); + + AssertThrottles( + udpClient2, + resendBytes * 0.75, landBytes * 0.75, windBytes * 0.75, cloudBytes * 0.75, taskBytes * 0.75, + textureBytes * 0.75, assetBytes * 0.75, totalBytes * 0.75, 0, totalBytes * 1.1); + } + + private void AssertThrottles( + LLUDPClient udpClient, + double resendBytes, double landBytes, double windBytes, double cloudBytes, double taskBytes, double textureBytes, double assetBytes, + double totalBytes, double targetBytes, double maxBytes) + { + ClientInfo ci = udpClient.GetClientInfo(); + +// Console.WriteLine( +// "Resend={0}, Land={1}, Wind={2}, Cloud={3}, Task={4}, Texture={5}, Asset={6}, TOTAL = {7}", +// ci.resendThrottle, ci.landThrottle, ci.windThrottle, ci.cloudThrottle, ci.taskThrottle, ci.textureThrottle, ci.assetThrottle, ci.totalThrottle); + + Assert.AreEqual((int)resendBytes, ci.resendThrottle, "Resend"); + Assert.AreEqual((int)landBytes, ci.landThrottle, "Land"); + Assert.AreEqual((int)windBytes, ci.windThrottle, "Wind"); + Assert.AreEqual((int)cloudBytes, ci.cloudThrottle, "Cloud"); + Assert.AreEqual((int)taskBytes, ci.taskThrottle, "Task"); + Assert.AreEqual((int)textureBytes, ci.textureThrottle, "Texture"); + Assert.AreEqual((int)assetBytes, ci.assetThrottle, "Asset"); + Assert.AreEqual((int)totalBytes, ci.totalThrottle, "Total"); + Assert.AreEqual((int)targetBytes, ci.targetThrottle, "Target"); + Assert.AreEqual((int)maxBytes, ci.maxThrottle, "Max"); + } + + private void SetThrottles( + LLUDPClient udpClient, int resendBytes, int landBytes, int windBytes, int cloudBytes, int taskBytes, int textureBytes, int assetBytes) + { + byte[] throttles = new byte[28]; + + Array.Copy(BitConverter.GetBytes((float)resendBytes * 8), 0, throttles, 0, 4); + Array.Copy(BitConverter.GetBytes((float)landBytes * 8), 0, throttles, 4, 4); + Array.Copy(BitConverter.GetBytes((float)windBytes * 8), 0, throttles, 8, 4); + Array.Copy(BitConverter.GetBytes((float)cloudBytes * 8), 0, throttles, 12, 4); + Array.Copy(BitConverter.GetBytes((float)taskBytes * 8), 0, throttles, 16, 4); + Array.Copy(BitConverter.GetBytes((float)textureBytes * 8), 0, throttles, 20, 4); + Array.Copy(BitConverter.GetBytes((float)assetBytes * 8), 0, throttles, 24, 4); + + udpClient.SetThrottles(throttles); + } + } + */ +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Asset/Tests/FlotsamAssetCacheTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Asset/Tests/FlotsamAssetCacheTests.cs new file mode 100644 index 00000000000..79d6909f91e --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Asset/Tests/FlotsamAssetCacheTests.cs @@ -0,0 +1,115 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Asset.Tests +{ + /// + /// At the moment we're only test the in-memory part of the FlotsamAssetCache. This is a considerable weakness. + /// + [TestFixture] + public class FlotsamAssetCacheTests : OpenSimTestCase + { + protected TestScene m_scene; + protected FlotsamAssetCache m_cache; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource config = new IniConfigSource(); + + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetCaching", "FlotsamAssetCache"); + config.AddConfig("AssetCache"); + config.Configs["AssetCache"].Set("FileCacheEnabled", "false"); + config.Configs["AssetCache"].Set("MemoryCacheEnabled", "true"); + + m_cache = new FlotsamAssetCache(); + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, config, m_cache); + } + + [Test] + public void TestCacheAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + AssetBase asset = AssetHelpers.CreateNotecardAsset(); + asset.ID = TestHelpers.ParseTail(0x1).ToString(); + + // Check we don't get anything before the asset is put in the cache + AssetBase retrievedAsset = m_cache.Get(asset.ID.ToString()); + Assert.That(retrievedAsset, Is.Null); + + m_cache.Store(asset); + + // Check that asset is now in cache + retrievedAsset = m_cache.Get(asset.ID.ToString()); + Assert.That(retrievedAsset, Is.Not.Null); + Assert.That(retrievedAsset.ID, Is.EqualTo(asset.ID)); + } + + [Test] + public void TestExpireAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + AssetBase asset = AssetHelpers.CreateNotecardAsset(); + asset.ID = TestHelpers.ParseTail(0x2).ToString(); + + m_cache.Store(asset); + + m_cache.Expire(asset.ID); + + AssetBase retrievedAsset = m_cache.Get(asset.ID.ToString()); + Assert.That(retrievedAsset, Is.Null); + } + + [Test] + public void TestClearCache() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + AssetBase asset = AssetHelpers.CreateNotecardAsset(); + asset.ID = TestHelpers.ParseTail(0x2).ToString(); + + m_cache.Store(asset); + + m_cache.Clear(); + + AssetBase retrievedAsset = m_cache.Get(asset.ID.ToString()); + Assert.That(retrievedAsset, Is.Null); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Attachments/Tests/AttachmentsModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Attachments/Tests/AttachmentsModuleTests.cs new file mode 100644 index 00000000000..1fd5a911298 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Attachments/Tests/AttachmentsModuleTests.cs @@ -0,0 +1,1029 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Timers; +using System.Xml; +using Timer=System.Timers.Timer; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.CoreModules.Avatar.Attachments; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.CoreModules.Scripting.WorldComm; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.XEngine; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Attachments.Tests +{ +/* + /// + /// Attachment tests + /// + [TestFixture] + public class AttachmentsModuleTests : OpenSimTestCase + { + private AutoResetEvent m_chatEvent = new AutoResetEvent(false); +// private OSChatMessage m_osChatMessageReceived; + + // Used to test whether the operations have fired the attach event. Must be reset after each test. + private int m_numberOfAttachEventsFired; + + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.None; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten not to worry about such things. + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + private void OnChatFromWorld(object sender, OSChatMessage oscm) + { +// Console.WriteLine("Got chat [{0}]", oscm.Message); + +// m_osChatMessageReceived = oscm; + m_chatEvent.Set(); + } + + private Scene CreateTestScene() + { + IConfigSource config = new IniConfigSource(); + List modules = new List(); + + AddCommonConfig(config, modules); + + Scene scene + = new SceneHelpers().SetupScene( + "attachments-test-scene", TestHelpers.ParseTail(999), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, modules.ToArray()); + + scene.EventManager.OnAttach += (localID, itemID, avatarID) => m_numberOfAttachEventsFired++; + + return scene; + } + + private Scene CreateScriptingEnabledTestScene() + { + IConfigSource config = new IniConfigSource(); + List modules = new List(); + + AddCommonConfig(config, modules); + AddScriptingConfig(config, modules); + + Scene scene + = new SceneHelpers().SetupScene( + "attachments-test-scene", TestHelpers.ParseTail(999), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, modules.ToArray()); + + scene.StartScripts(); + + return scene; + } + + private void AddCommonConfig(IConfigSource config, List modules) + { + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + AttachmentsModule attMod = new AttachmentsModule(); + attMod.DebugLevel = 1; + modules.Add(attMod); + modules.Add(new BasicInventoryAccessModule()); + } + + private void AddScriptingConfig(IConfigSource config, List modules) + { + IConfig startupConfig = config.AddConfig("Startup"); + startupConfig.Set("DefaultScriptEngine", "XEngine"); + + IConfig xEngineConfig = config.AddConfig("XEngine"); + xEngineConfig.Set("Enabled", "true"); + xEngineConfig.Set("StartDelay", "0"); + + // These tests will not run with AppDomainLoading = true, at least on mono. For unknown reasons, the call + // to AssemblyResolver.OnAssemblyResolve fails. + xEngineConfig.Set("AppDomainLoading", "false"); + + modules.Add(new XEngine()); + + // Necessary to stop serialization complaining + // FIXME: Stop this being necessary if at all possible +// modules.Add(new WorldCommModule()); + } + + /// + /// Creates an attachment item in the given user's inventory. Does not attach. + /// + /// + /// A user with the given ID and an inventory must already exist. + /// + /// + /// The attachment item. + /// + /// + /// + /// + /// + /// + private InventoryItemBase CreateAttachmentItem( + Scene scene, UUID userId, string attName, int rawItemId, int rawAssetId) + { + return UserInventoryHelpers.CreateInventoryItem( + scene, + attName, + TestHelpers.ParseTail(rawItemId), + TestHelpers.ParseTail(rawAssetId), + userId, + InventoryType.Object); + } + + [Test] + public void TestAddAttachmentFromGround() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + m_numberOfAttachEventsFired = 0; + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1); + + string attName = "att"; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, attName, sp.UUID); + Assert.That(so.Backup, Is.True); + + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attName)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.That(attSo.Backup, Is.False); + + // Check item status +// Assert.That( +// sp.Appearance.GetAttachpoint(attSo.FromItemID), +// Is.EqualTo((int)AttachmentPoint.Chest)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(sp.UUID, attSo.FromItemID); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(attName)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + [Test] + public void TestWearAttachmentFromGround() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1); + + SceneObjectGroup so2 = SceneHelpers.AddSceneObject(scene, "att2", sp.UUID); + + { + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "att1", sp.UUID); + + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Default, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(so.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check item status +// Assert.That( +// sp.Appearance.GetAttachpoint(attSo.FromItemID), +// Is.EqualTo((int)AttachmentPoint.LeftHand)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(sp.UUID, attSo.FromItemID); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(so.Name)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(2)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + // Test wearing a different attachment from the ground. + { + scene.AttachmentsModule.AttachObject(sp, so2, (uint)AttachmentPoint.Default, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(so2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check item status +// Assert.That( +// sp.Appearance.GetAttachpoint(attSo.FromItemID), +// Is.EqualTo((int)AttachmentPoint.LeftHand)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(sp.UUID, attSo.FromItemID); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(so2.Name)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + + // Test rewearing an already worn attachment from ground. Nothing should happen. + { + scene.AttachmentsModule.AttachObject(sp, so2, (uint)AttachmentPoint.Default, false, true, false); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(so2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check item status +// Assert.That( +// sp.Appearance.GetAttachpoint(attSo.FromItemID), +// Is.EqualTo((int)AttachmentPoint.LeftHand)); + + InventoryItemBase attachmentItem = scene.InventoryService.GetItem(sp.UUID, attSo.FromItemID); + Assert.That(attachmentItem, Is.Not.Null); + Assert.That(attachmentItem.Name, Is.EqualTo(so2.Name)); + + InventoryFolderBase targetFolder = scene.InventoryService.GetFolderForType(sp.UUID, FolderType.Object); + Assert.That(attachmentItem.Folder, Is.EqualTo(targetFolder.ID)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + } + + /// + /// Test that we do not attempt to attach an in-world object that someone else is sitting on. + /// + [Test] + public void TestAddSatOnAttachmentFromGround() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + m_numberOfAttachEventsFired = 0; + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1); + + string attName = "att"; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, attName, sp.UUID); + + UserAccount ua2 = UserAccountHelpers.CreateUserWithInventory(scene, 0x2); + ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, ua2); + + // Put avatar within 10m of the prim so that sit doesn't fail. + sp2.AbsolutePosition = new Vector3(0, 0, 0); + sp2.HandleAgentRequestSit(sp2.ControllingClient, sp2.UUID, so.UUID, Vector3.Zero); + + scene.AttachmentsModule.AttachObject(sp, so, (uint)AttachmentPoint.Chest, false, true, false); + + Assert.That(sp.HasAttachments(), Is.False); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); + } + + [Test] + public void TestRezAttachmentFromInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1.PrincipalID); + + InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); + + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory( + sp, attItem.ID, (uint)AttachmentPoint.Chest); + + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.IsFalse(attSo.Backup); + + // Check appearance status +// Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); +// Assert.That(sp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + // Test attaching an already attached attachment + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory( + sp, attItem.ID, (uint)AttachmentPoint.Chest); + + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check appearance status +// Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); +// Assert.That(sp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + } + + /// + /// Test wearing an attachment from inventory, as opposed to explicit choosing the rez point + /// + [Test] + public void TestWearAttachmentFromInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1.PrincipalID); + + InventoryItemBase attItem1 = CreateAttachmentItem(scene, ua1.PrincipalID, "att1", 0x10, 0x20); + InventoryItemBase attItem2 = CreateAttachmentItem(scene, ua1.PrincipalID, "att2", 0x11, 0x21); + + { + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, attItem1.ID, (uint)AttachmentPoint.Default); + + // default attachment point is currently the left hand. + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem1.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + + // Check appearance status +// Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); +// Assert.That(sp.Appearance.GetAttachpoint(attItem1.ID), Is.EqualTo((int)AttachmentPoint.LeftHand)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + // Test wearing a second attachment at the same position + // Until multiple attachments at one point is implemented, this will remove the first attachment + // This test relies on both attachments having the same default attachment point (in this case LeftHand + // since none other has been set). + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, attItem2.ID, (uint)AttachmentPoint.Default); + + // default attachment point is currently the left hand. + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + + // Check appearance status +// Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); +// Assert.That(sp.Appearance.GetAttachpoint(attItem2.ID), Is.EqualTo((int)AttachmentPoint.LeftHand)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + + // Test wearing an already attached attachment + { + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, attItem2.ID, (uint)AttachmentPoint.Default); + + // default attachment point is currently the left hand. + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem2.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.LeftHand)); + Assert.That(attSo.IsAttachment); + + // Check appearance status +// Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(1)); +// Assert.That(sp.Appearance.GetAttachpoint(attItem2.ID), Is.EqualTo((int)AttachmentPoint.LeftHand)); + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(3)); + } + } + + /// + /// Test specific conditions associated with rezzing a scripted attachment from inventory. + /// + [Test] + public void TestRezScriptedAttachmentFromInventory() + { + TestHelpers.InMethod(); + + Scene scene = CreateScriptingEnabledTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1); + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, sp.UUID, "att-name", 0x10); + TaskInventoryItem scriptItem + = TaskInventoryHelpers.AddScript( + scene.AssetService, + so.RootPart, + "scriptItem", + "default { attach(key id) { if (id != NULL_KEY) { llSay(0, \"Hello World\"); } } }"); + + InventoryItemBase userItem = UserInventoryHelpers.AddInventoryItem(scene, so, 0x100, 0x1000); + + // FIXME: Right now, we have to do a tricksy chat listen to make sure we know when the script is running. + // In the future, we need to be able to do this programatically more predicably. + scene.EventManager.OnChatFromWorld += OnChatFromWorld; + + m_chatEvent.Reset(); + scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, userItem.ID, (uint)AttachmentPoint.Chest); + + m_chatEvent.WaitOne(60000); + + // TODO: Need to have a test that checks the script is actually started but this involves a lot more + // plumbing of the script engine and either pausing for events or more infrastructure to turn off various + // script engine delays/asychronicity that isn't helpful in an automated regression testing context. + SceneObjectGroup attSo = scene.GetSceneObjectGroup(so.Name); + Assert.That(attSo.ContainsScripts(), Is.True); + + TaskInventoryItem reRezzedScriptItem = attSo.RootPart.Inventory.GetInventoryItem(scriptItem.Name); + IScriptModule xengine = scene.RequestModuleInterface(); + Assert.That(xengine.GetScriptState(reRezzedScriptItem.ItemID), Is.True); + } + + [Test] + public void TestDetachAttachmentToGround() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1.PrincipalID); + + InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); + + ISceneEntity so + = scene.AttachmentsModule.RezSingleAttachmentFromInventory( + sp, attItem.ID, (uint)AttachmentPoint.Chest); + + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.DetachSingleAttachmentToGround(sp, so.LocalId); + + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.False); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(0)); + + // Check appearance status +// Assert.That(sp.Appearance.GetAttachments().Count, Is.EqualTo(0)); + + // Check item status + Assert.That(scene.InventoryService.GetItem(sp.UUID, attItem.ID), Is.Null); + + // Check object in scene + SceneObjectGroup soInScene = scene.GetSceneObjectGroup("att"); + Assert.That(soInScene, Is.Not.Null); + Assert.IsTrue(soInScene.Backup); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + [Test] + public void TestDetachAttachmentToInventory() + { + TestHelpers.InMethod(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1.PrincipalID); + + InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); + + SceneObjectGroup so + = (SceneObjectGroup)scene.AttachmentsModule.RezSingleAttachmentFromInventory( + sp, attItem.ID, (uint)AttachmentPoint.Chest); + + m_numberOfAttachEventsFired = 0; + scene.AttachmentsModule.DetachSingleAttachmentToInv(sp, so); + + // Check status on scene presence + Assert.That(sp.HasAttachments(), Is.False); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(0)); + + // Check item status +// Assert.That(sp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo(0)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(0)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + /// + /// Test specific conditions associated with detaching a scripted attachment from inventory. + /// + [Test] + public void TestDetachScriptedAttachmentToInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateScriptingEnabledTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, ua1); + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, sp.UUID, "att-name", 0x10); + TaskInventoryItem scriptTaskItem + = TaskInventoryHelpers.AddScript( + scene.AssetService, + so.RootPart, + "scriptItem", + "default { attach(key id) { if (id != NULL_KEY) { llSay(0, \"Hello World\"); } } }"); + + InventoryItemBase userItem = UserInventoryHelpers.AddInventoryItem(scene, so, 0x100, 0x1000); + + // FIXME: Right now, we have to do a tricksy chat listen to make sure we know when the script is running. + // In the future, we need to be able to do this programatically more predicably. + scene.EventManager.OnChatFromWorld += OnChatFromWorld; + + m_chatEvent.Reset(); + SceneObjectGroup rezzedSo + = (SceneObjectGroup)(scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, userItem.ID, (uint)AttachmentPoint.Chest)); + + // Wait for chat to signal rezzed script has been started. + m_chatEvent.WaitOne(60000); + + scene.AttachmentsModule.DetachSingleAttachmentToInv(sp, rezzedSo); + + InventoryItemBase userItemUpdated = scene.InventoryService.GetItem(userItem.Owner, userItem.ID); + AssetBase asset = scene.AssetService.Get(userItemUpdated.AssetID.ToString()); + + // TODO: It would probably be better here to check script state via the saving and retrieval of state + // information at a higher level, rather than having to inspect the serialization. + XmlDocument soXml = new XmlDocument(); + soXml.LoadXml(Encoding.UTF8.GetString(asset.Data)); + + XmlNodeList scriptStateNodes = soXml.GetElementsByTagName("ScriptState"); + Assert.That(scriptStateNodes.Count, Is.EqualTo(1)); + + // Re-rez the attachment to check script running state + SceneObjectGroup reRezzedSo = (SceneObjectGroup)(scene.AttachmentsModule.RezSingleAttachmentFromInventory(sp, userItem.ID, (uint)AttachmentPoint.Chest)); + + // Wait for chat to signal rezzed script has been started. + m_chatEvent.WaitOne(60000); + + TaskInventoryItem reRezzedScriptItem = reRezzedSo.RootPart.Inventory.GetInventoryItem(scriptTaskItem.Name); + IScriptModule xengine = scene.RequestModuleInterface(); + Assert.That(xengine.GetScriptState(reRezzedScriptItem.ItemID), Is.True); + +// Console.WriteLine(soXml.OuterXml); + } + + /// + /// Test that attachments don't hang about in the scene when the agent is closed + /// + [Test] + public void TestRemoveAttachmentsOnAvatarExit() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + acd.Appearance = new AvatarAppearance(); + acd.Appearance.SetAttachment((int)AttachmentPoint.Chest, attItem.ID, attItem.AssetID); + ScenePresence presence = SceneHelpers.AddScenePresence(scene, acd); + + UUID rezzedAttID = presence.GetAttachments()[0].UUID; + + m_numberOfAttachEventsFired = 0; + scene.CloseAgent(presence.UUID, false); + + // Check that we can't retrieve this attachment from the scene. + Assert.That(scene.GetSceneObjectGroup(rezzedAttID), Is.Null); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); + } + + [Test] + public void TestRezAttachmentsOnAvatarEntrance() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + acd.Appearance = new AvatarAppearance(); + acd.Appearance.SetAttachment((int)AttachmentPoint.Chest, attItem.ID, attItem.AssetID); + + m_numberOfAttachEventsFired = 0; + ScenePresence presence = SceneHelpers.AddScenePresence(scene, acd); + + Assert.That(presence.HasAttachments(), Is.True); + List attachments = presence.GetAttachments(); + + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(attItem.Name)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.IsFalse(attSo.Backup); + + // Check appearance status + List retreivedAttachments = presence.Appearance.GetAttachments(); + Assert.That(retreivedAttachments.Count, Is.EqualTo(1)); + Assert.That(retreivedAttachments[0].AttachPoint, Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(retreivedAttachments[0].ItemID, Is.EqualTo(attItem.ID)); + Assert.That(retreivedAttachments[0].AssetID, Is.EqualTo(attItem.AssetID)); + Assert.That(presence.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + Assert.That(scene.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check events. We expect OnAttach to fire on login. + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(1)); + } + + [Test] + public void TestUpdateAttachmentPosition() + { + TestHelpers.InMethod(); + + Scene scene = CreateTestScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene, 0x1); + InventoryItemBase attItem = CreateAttachmentItem(scene, ua1.PrincipalID, "att", 0x10, 0x20); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + acd.Appearance = new AvatarAppearance(); + acd.Appearance.SetAttachment((int)AttachmentPoint.Chest, attItem.ID, attItem.AssetID); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, acd); + + SceneObjectGroup attSo = sp.GetAttachments()[0]; + + Vector3 newPosition = new Vector3(1, 2, 4); + + m_numberOfAttachEventsFired = 0; + scene.SceneGraph.UpdatePrimGroupPosition(attSo.LocalId, newPosition, sp.ControllingClient); + + Assert.That(attSo.AbsolutePosition, Is.EqualTo(sp.AbsolutePosition)); + Assert.That(attSo.RootPart.AttachedPos, Is.EqualTo(newPosition)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); + } + + + [Test] + public void TestSameSimulatorNeighbouringRegionsTeleportV1() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + + AttachmentsModule attModA = new AttachmentsModule(); + AttachmentsModule attModB = new AttachmentsModule(); + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + + modulesConfig.Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1001, 1000); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules( + sceneA, config, new CapabilitiesModule(), etmA, attModA, new BasicInventoryAccessModule()); + SceneHelpers.SetupSceneModules( + sceneB, config, new CapabilitiesModule(), etmB, attModB, new BasicInventoryAccessModule()); + + // FIXME: Hack - this is here temporarily to revert back to older entity transfer behaviour + //lscm.ServiceVersion = 0.1f; + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(sceneA, 0x1); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeTeleportSp.AbsolutePosition = new Vector3(30, 31, 32); + + InventoryItemBase attItem = CreateAttachmentItem(sceneA, ua1.PrincipalID, "att", 0x10, 0x20); + + sceneA.AttachmentsModule.RezSingleAttachmentFromInventory( + beforeTeleportSp, attItem.ID, (uint)AttachmentPoint.Chest); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + m_numberOfAttachEventsFired = 0; + sceneA.RequestTeleportLocation( + beforeTeleportSp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + destinationTestClients[0].CompleteMovement(); + + // Check attachments have made it into sceneB + ScenePresence afterTeleportSceneBSp = sceneB.GetScenePresence(ua1.PrincipalID); + + // This is appearance data, as opposed to actually rezzed attachments + List sceneBAttachments = afterTeleportSceneBSp.Appearance.GetAttachments(); + Assert.That(sceneBAttachments.Count, Is.EqualTo(1)); + Assert.That(sceneBAttachments[0].AttachPoint, Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(sceneBAttachments[0].ItemID, Is.EqualTo(attItem.ID)); + Assert.That(sceneBAttachments[0].AssetID, Is.EqualTo(attItem.AssetID)); + Assert.That(afterTeleportSceneBSp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + // This is the actual attachment + List actualSceneBAttachments = afterTeleportSceneBSp.GetAttachments(); + Assert.That(actualSceneBAttachments.Count, Is.EqualTo(1)); + SceneObjectGroup actualSceneBAtt = actualSceneBAttachments[0]; + Assert.That(actualSceneBAtt.Name, Is.EqualTo(attItem.Name)); + Assert.That(actualSceneBAtt.AttachmentPoint, Is.EqualTo((uint)AttachmentPoint.Chest)); + Assert.IsFalse(actualSceneBAtt.Backup); + + Assert.That(sceneB.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check attachments have been removed from sceneA + ScenePresence afterTeleportSceneASp = sceneA.GetScenePresence(ua1.PrincipalID); + + // Since this is appearance data, it is still present on the child avatar! + List sceneAAttachments = afterTeleportSceneASp.Appearance.GetAttachments(); + Assert.That(sceneAAttachments.Count, Is.EqualTo(1)); + Assert.That(afterTeleportSceneASp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + // This is the actual attachment, which should no longer exist + List actualSceneAAttachments = afterTeleportSceneASp.GetAttachments(); + Assert.That(actualSceneAAttachments.Count, Is.EqualTo(0)); + + Assert.That(sceneA.GetSceneObjectGroups().Count, Is.EqualTo(0)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); + } + + + [Test] + public void TestSameSimulatorNeighbouringRegionsTeleportV2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + + AttachmentsModule attModA = new AttachmentsModule(); + AttachmentsModule attModB = new AttachmentsModule(); + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + modulesConfig.Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1001, 1000); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules( + sceneA, config, new CapabilitiesModule(), etmA, attModA, new BasicInventoryAccessModule()); + SceneHelpers.SetupSceneModules( + sceneB, config, new CapabilitiesModule(), etmB, attModB, new BasicInventoryAccessModule()); + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(sceneA, 0x1); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(ua1.PrincipalID); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeTeleportSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeTeleportSp.AbsolutePosition = new Vector3(30, 31, 32); + + Assert.That(destinationTestClients.Count, Is.EqualTo(1)); + Assert.That(destinationTestClients[0], Is.Not.Null); + + InventoryItemBase attItem = CreateAttachmentItem(sceneA, ua1.PrincipalID, "att", 0x10, 0x20); + + sceneA.AttachmentsModule.RezSingleAttachmentFromInventory( + beforeTeleportSp, attItem.ID, (uint)AttachmentPoint.Chest); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + // Here, we need to make clientA's receipt of SendRegionTeleport trigger clientB's CompleteMovement(). This + // is to operate the teleport V2 mechanism where the EntityTransferModule will first request the client to + // CompleteMovement to the region and then call UpdateAgent to the destination region to confirm the receipt + // Both these operations will occur on different threads and will wait for each other. + // We have to do this via ThreadPool directly since FireAndForget has been switched to sync for the V1 + // test protocol, where we are trying to avoid unpredictable async operations in regression tests. + tc.OnTestClientSendRegionTeleport + += (regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL) + => ThreadPool.UnsafeQueueUserWorkItem(o => destinationTestClients[0].CompleteMovement(), null); + + m_numberOfAttachEventsFired = 0; + sceneA.RequestTeleportLocation( + beforeTeleportSp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // Check attachments have made it into sceneB + ScenePresence afterTeleportSceneBSp = sceneB.GetScenePresence(ua1.PrincipalID); + + // This is appearance data, as opposed to actually rezzed attachments + List sceneBAttachments = afterTeleportSceneBSp.Appearance.GetAttachments(); + Assert.That(sceneBAttachments.Count, Is.EqualTo(1)); + Assert.That(sceneBAttachments[0].AttachPoint, Is.EqualTo((int)AttachmentPoint.Chest)); + Assert.That(sceneBAttachments[0].ItemID, Is.EqualTo(attItem.ID)); + Assert.That(sceneBAttachments[0].AssetID, Is.EqualTo(attItem.AssetID)); + Assert.That(afterTeleportSceneBSp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + // This is the actual attachment + List actualSceneBAttachments = afterTeleportSceneBSp.GetAttachments(); + Assert.That(actualSceneBAttachments.Count, Is.EqualTo(1)); + SceneObjectGroup actualSceneBAtt = actualSceneBAttachments[0]; + Assert.That(actualSceneBAtt.Name, Is.EqualTo(attItem.Name)); + Assert.That(actualSceneBAtt.AttachmentPoint, Is.EqualTo((uint)AttachmentPoint.Chest)); + Assert.IsFalse(actualSceneBAtt.Backup); + + Assert.That(sceneB.GetSceneObjectGroups().Count, Is.EqualTo(1)); + + // Check attachments have been removed from sceneA + ScenePresence afterTeleportSceneASp = sceneA.GetScenePresence(ua1.PrincipalID); + + // Since this is appearance data, it is still present on the child avatar! + List sceneAAttachments = afterTeleportSceneASp.Appearance.GetAttachments(); + Assert.That(sceneAAttachments.Count, Is.EqualTo(1)); + Assert.That(afterTeleportSceneASp.Appearance.GetAttachpoint(attItem.ID), Is.EqualTo((int)AttachmentPoint.Chest)); + + // This is the actual attachment, which should no longer exist + List actualSceneAAttachments = afterTeleportSceneASp.GetAttachments(); + Assert.That(actualSceneAAttachments.Count, Is.EqualTo(0)); + + Assert.That(sceneA.GetSceneObjectGroups().Count, Is.EqualTo(0)); + + // Check events + Assert.That(m_numberOfAttachEventsFired, Is.EqualTo(0)); + } + } +*/ +} diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs new file mode 100644 index 00000000000..804bde09df8 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/AvatarFactory/Tests/AvatarFactoryModuleTests.cs @@ -0,0 +1,193 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.AvatarFactory +{ + [TestFixture] + public class AvatarFactoryModuleTests : OpenSimTestCase + { + /// + /// Only partial right now since we don't yet test that it's ended up in the avatar appearance service. + /// + [Test] + public void TestSetAppearance() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + UUID bakedTextureID = TestHelpers.ParseTail(0x2); + + // We need an asset cache because otherwise the LocalAssetServiceConnector will short-circuit directly + // to the AssetService, which will then store temporary and local assets permanently + TestsAssetCache assetCache = new TestsAssetCache(); + + AvatarFactoryModule afm = new AvatarFactoryModule(); + TestScene scene = new SceneHelpers(assetCache).SetupScene(); + SceneHelpers.SetupSceneModules(scene, afm); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId); + + // TODO: Use the actual BunchOfCaps functionality once we slot in the CapabilitiesModules + AssetBase bakedTextureAsset; + bakedTextureAsset + = new AssetBase( + bakedTextureID, "Test Baked Texture", (sbyte)AssetType.Texture, userId.ToString()); + bakedTextureAsset.Data = new byte[] { 2 }; // Not necessary to have a genuine JPEG2000 asset here yet + bakedTextureAsset.Temporary = true; + bakedTextureAsset.Local = true; + scene.AssetService.Store(bakedTextureAsset); + + byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT]; + for (byte i = 0; i < visualParams.Length; i++) + visualParams[i] = i; + + Primitive.TextureEntry bakedTextureEntry = new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)); + uint eyesFaceIndex = (uint)AppearanceManager.BakeTypeToAgentTextureIndex(BakeType.Eyes); + Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex); + + int rebakeRequestsReceived = 0; + ((TestClient)sp.ControllingClient).OnReceivedSendRebakeAvatarTextures += id => rebakeRequestsReceived++; + + // This is the alpha texture + eyesFace.TextureID = bakedTextureID; + afm.SetAppearance(sp, bakedTextureEntry, visualParams, null); + + Assert.That(rebakeRequestsReceived, Is.EqualTo(0)); + + AssetBase eyesBake = scene.AssetService.Get(bakedTextureID.ToString()); + Assert.That(eyesBake, Is.Not.Null); + Assert.That(eyesBake.Temporary, Is.True); + Assert.That(eyesBake.Local, Is.True); + } + + /// + /// Test appearance setting where the baked texture UUID are library alpha textures. + /// + /// + /// For a mesh avatar, it appears these 'baked textures' are used. So these should not trigger a request to + /// rebake. + /// + [Test] + public void TestSetAppearanceAlphaBakedTextures() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + UUID alphaTextureID = new UUID("3a367d1c-bef1-6d43-7595-e88c1e3aadb3"); + + // We need an asset cache because otherwise the LocalAssetServiceConnector will short-circuit directly + // to the AssetService, which will then store temporary and local assets permanently + TestsAssetCache assetCache = new TestsAssetCache(); + + AvatarFactoryModule afm = new AvatarFactoryModule(); + TestScene scene = new SceneHelpers(assetCache).SetupScene(); + SceneHelpers.SetupSceneModules(scene, afm); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId); + + AssetBase libraryAsset; + libraryAsset + = new AssetBase( + alphaTextureID, "Default Alpha Layer Texture", (sbyte)AssetType.Texture, userId.ToString()); + libraryAsset.Data = new byte[] { 2 }; // Not necessary to have a genuine JPEG2000 asset here yet + libraryAsset.Temporary = false; + libraryAsset.Local = false; + scene.AssetService.Store(libraryAsset); + + byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT]; + for (byte i = 0; i < visualParams.Length; i++) + visualParams[i] = i; + + Primitive.TextureEntry bakedTextureEntry = new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)); + uint eyesFaceIndex = (uint)AppearanceManager.BakeTypeToAgentTextureIndex(BakeType.Eyes); + Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex); + + int rebakeRequestsReceived = 0; + ((TestClient)sp.ControllingClient).OnReceivedSendRebakeAvatarTextures += id => rebakeRequestsReceived++; + + // This is the alpha texture + eyesFace.TextureID = alphaTextureID; + afm.SetAppearance(sp, bakedTextureEntry, visualParams, null); + + Assert.That(rebakeRequestsReceived, Is.EqualTo(0)); + } + + [Test] + public void TestSaveBakedTextures() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + UUID eyesTextureId = TestHelpers.ParseTail(0x2); + + // We need an asset cache because otherwise the LocalAssetServiceConnector will short-circuit directly + // to the AssetService, which will then store temporary and local assets permanently + TestsAssetCache assetCache = new TestsAssetCache(); + + AvatarFactoryModule afm = new AvatarFactoryModule(); + TestScene scene = new SceneHelpers(assetCache).SetupScene(); + SceneHelpers.SetupSceneModules(scene, afm); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, userId); + + // TODO: Use the actual BunchOfCaps functionality once we slot in the CapabilitiesModules + AssetBase uploadedAsset; + uploadedAsset = new AssetBase(eyesTextureId, "Baked Texture", (sbyte)AssetType.Texture, userId.ToString()); + uploadedAsset.Data = new byte[] { 2 }; + uploadedAsset.Temporary = true; + uploadedAsset.Local = true; // Local assets aren't persisted, non-local are + scene.AssetService.Store(uploadedAsset); + + byte[] visualParams = new byte[AvatarAppearance.VISUALPARAM_COUNT]; + for (byte i = 0; i < visualParams.Length; i++) + visualParams[i] = i; + + Primitive.TextureEntry bakedTextureEntry = new Primitive.TextureEntry(TestHelpers.ParseTail(0x10)); + uint eyesFaceIndex = (uint)AppearanceManager.BakeTypeToAgentTextureIndex(BakeType.Eyes); + Primitive.TextureEntryFace eyesFace = bakedTextureEntry.CreateFace(eyesFaceIndex); + eyesFace.TextureID = eyesTextureId; + + afm.SetAppearance(sp, bakedTextureEntry, visualParams, new WearableCacheItem[0]); + afm.SaveBakedTextures(userId); +// Dictionary bakedTextures = afm.GetBakedTextureFaces(userId); + + // We should also inpsect the asset data store layer directly, but this is difficult to get at right now. + assetCache.Clear(); + + AssetBase eyesBake = scene.AssetService.Get(eyesTextureId.ToString()); + Assert.That(eyesBake, Is.Not.Null); + Assert.That(eyesBake.Temporary, Is.False); + Assert.That(eyesBake.Local, Is.False); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Chat/Tests/ChatModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Chat/Tests/ChatModuleTests.cs new file mode 100644 index 00000000000..2dcfbaa1275 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Chat/Tests/ChatModuleTests.cs @@ -0,0 +1,313 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Chat.Tests +{ + [TestFixture] + public class ChatModuleTests : OpenSimTestCase + { + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + // We must do this here so that child agent positions are updated in a predictable manner. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + private void SetupNeighbourRegions(TestScene sceneA, TestScene sceneB) + { + // XXX: HTTP server is not (and should not be) necessary for this test, though it's absence makes the + // CapabilitiesModule complain when it can't set up HTTP endpoints. + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + + // We need entity transfer modules so that when sp2 logs into the east region, the region calls + // EntityTransferModuleto set up a child agent on the west region. + // XXX: However, this is not an entity transfer so is misleading. + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Chat"); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA, new ChatModule()); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB, new ChatModule()); + } + + /// + /// Tests chat between neighbour regions on the east-west axis + /// + /// + /// Really, this is a combination of a child agent position update test and a chat range test. These need + /// to be separated later on. + /// + [Test] + public void TestInterRegionChatDistanceEastWest() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID sp1Uuid = TestHelpers.ParseTail(0x11); + UUID sp2Uuid = TestHelpers.ParseTail(0x12); + + Vector3 sp1Position = new Vector3(6, 128, 20); + Vector3 sp2Position = new Vector3(250, 128, 20); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneWest = sh.SetupScene("sceneWest", TestHelpers.ParseTail(0x1), 1000, 1000); + TestScene sceneEast = sh.SetupScene("sceneEast", TestHelpers.ParseTail(0x2), 1001, 1000); + + SetupNeighbourRegions(sceneWest, sceneEast); + + ScenePresence sp1 = SceneHelpers.AddScenePresence(sceneEast, sp1Uuid); + TestClient sp1Client = (TestClient)sp1.ControllingClient; + + // If we don't set agents to flying, test will go wrong as they instantly fall to z = 0. + // TODO: May need to create special complete no-op test physics module rather than basic physics, since + // physics is irrelevant to this test. + sp1.Flying = true; + + // When sp1 logs in to sceneEast, it sets up a child agent in sceneWest and informs the sp2 client to + // make the connection. For this test, will simplify this chain by making the connection directly. + ScenePresence sp1Child = SceneHelpers.AddChildScenePresence(sceneWest, sp1Uuid); + TestClient sp1ChildClient = (TestClient)sp1Child.ControllingClient; + + sp1.AbsolutePosition = sp1Position; + + ScenePresence sp2 = SceneHelpers.AddScenePresence(sceneWest, sp2Uuid); + TestClient sp2Client = (TestClient)sp2.ControllingClient; + sp2.Flying = true; + + ScenePresence sp2Child = SceneHelpers.AddChildScenePresence(sceneEast, sp2Uuid); + TestClient sp2ChildClient = (TestClient)sp2Child.ControllingClient; + + sp2.AbsolutePosition = sp2Position; + + // We must update the scenes in order to make the root new root agents trigger position updates in their + // children. + for (int i = 0; i < 6; ++i) + { + sceneWest.Update(1); + sceneEast.Update(1); + } + sp1.DrawDistance += 64; + sp2.DrawDistance += 64; + for (int i = 0; i < 6; ++i) + { + sceneWest.Update(1); + sceneEast.Update(1); + } + + // Check child positions are correct. + Assert.AreEqual( + new Vector3(sp1Position.X + sceneEast.RegionInfo.RegionSizeX, sp1Position.Y, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + Assert.AreEqual( + new Vector3(sp2Position.X - sceneWest.RegionInfo.RegionSizeX, sp2Position.Y, sp2Position.Z), + sp2ChildClient.SceneAgent.AbsolutePosition); + + string receivedSp1ChatMessage = ""; + string receivedSp2ChatMessage = ""; + + sp1ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp1ChatMessage = message; + sp2ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp2ChatMessage = message; + + TestUserInRange(sp1Client, "ello darling", ref receivedSp2ChatMessage); + TestUserInRange(sp2Client, "fantastic cats", ref receivedSp1ChatMessage); + + sp1Position = new Vector3(30, 128, 20); + sp1.AbsolutePosition = sp1Position; + for (int i = 0; i < 2; ++i) + { + sceneWest.Update(1); + sceneEast.Update(1); + } + Thread.Sleep(12000); // child updates are now time limited + for (int i = 0; i < 6; ++i) + { + sceneWest.Update(1); + sceneEast.Update(1); + } + + // Check child position is correct. + Assert.AreEqual( + new Vector3(sp1Position.X + sceneEast.RegionInfo.RegionSizeX, sp1Position.Y, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + TestUserOutOfRange(sp1Client, "beef", ref receivedSp2ChatMessage); + TestUserOutOfRange(sp2Client, "lentils", ref receivedSp1ChatMessage); + } + + /// + /// Tests chat between neighbour regions on the north-south axis + /// + /// + /// Really, this is a combination of a child agent position update test and a chat range test. These need + /// to be separated later on. + /// + [Test] + public void TestInterRegionChatDistanceNorthSouth() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + UUID sp1Uuid = TestHelpers.ParseTail(0x11); + UUID sp2Uuid = TestHelpers.ParseTail(0x12); + + Vector3 sp1Position = new Vector3(128, 250, 20); + Vector3 sp2Position = new Vector3(128, 6, 20); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneNorth = sh.SetupScene("sceneNorth", TestHelpers.ParseTail(0x1), 1000, 1000); + TestScene sceneSouth = sh.SetupScene("sceneSouth", TestHelpers.ParseTail(0x2), 1000, 1001); + + SetupNeighbourRegions(sceneNorth, sceneSouth); + + ScenePresence sp1 = SceneHelpers.AddScenePresence(sceneNorth, sp1Uuid); + TestClient sp1Client = (TestClient)sp1.ControllingClient; + + // If we don't set agents to flying, test will go wrong as they instantly fall to z = 0. + // TODO: May need to create special complete no-op test physics module rather than basic physics, since + // physics is irrelevant to this test. + sp1.Flying = true; + + // When sp1 logs in to sceneEast, it sets up a child agent in sceneNorth and informs the sp2 client to + // make the connection. For this test, will simplify this chain by making the connection directly. + ScenePresence sp1Child = SceneHelpers.AddChildScenePresence(sceneSouth, sp1Uuid); + TestClient sp1ChildClient = (TestClient)sp1Child.ControllingClient; + + sp1.AbsolutePosition = sp1Position; + + ScenePresence sp2 = SceneHelpers.AddScenePresence(sceneSouth, sp2Uuid); + TestClient sp2Client = (TestClient)sp2.ControllingClient; + sp2.Flying = true; + + ScenePresence sp2Child = SceneHelpers.AddChildScenePresence(sceneNorth, sp2Uuid); + TestClient sp2ChildClient = (TestClient)sp2Child.ControllingClient; + + sp2.AbsolutePosition = sp2Position; + + // We must update the scenes in order to make the root new root agents trigger position updates in their + // children. + for (int i = 0; i < 6; ++i) + { + sceneNorth.Update(1); + sceneSouth.Update(1); + } + sp1.DrawDistance += 64; + sp2.DrawDistance += 64; + for (int i = 0; i < 6; ++i) + { + sceneNorth.Update(1); + sceneSouth.Update(1); + } + + // Check child positions are correct. + Assert.AreEqual( + new Vector3(sp1Position.X, sp1Position.Y - sceneNorth.RegionInfo.RegionSizeY, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + Assert.AreEqual( + new Vector3(sp2Position.X, sp2Position.Y + sceneSouth.RegionInfo.RegionSizeY, sp2Position.Z), + sp2ChildClient.SceneAgent.AbsolutePosition); + + string receivedSp1ChatMessage = ""; + string receivedSp2ChatMessage = ""; + + sp1ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp1ChatMessage = message; + sp2ChildClient.OnReceivedChatMessage + += (message, type, fromPos, fromName, fromAgentID, ownerID, source, audible) => receivedSp2ChatMessage = message; + + TestUserInRange(sp1Client, "ello darling", ref receivedSp2ChatMessage); + TestUserInRange(sp2Client, "fantastic cats", ref receivedSp1ChatMessage); + + sp1Position = new Vector3(30, 128, 20); + sp1.AbsolutePosition = sp1Position; + sceneNorth.Update(6); + sceneSouth.Update(6); + Thread.Sleep(12000); // child updates are now time limited + sceneNorth.Update(6); + sceneSouth.Update(6); + + // Check child position is correct. + Assert.AreEqual( + new Vector3(sp1Position.X, sp1Position.Y - sceneNorth.RegionInfo.RegionSizeY, sp1Position.Z), + sp1ChildClient.SceneAgent.AbsolutePosition); + + TestUserOutOfRange(sp1Client, "beef", ref receivedSp2ChatMessage); + TestUserOutOfRange(sp2Client, "lentils", ref receivedSp1ChatMessage); + } + + private void TestUserInRange(TestClient speakClient, string testMessage, ref string receivedMessage) + { + receivedMessage = ""; + + speakClient.Chat(0, ChatTypeEnum.Say, testMessage); + + Assert.AreEqual(testMessage, receivedMessage); + } + + private void TestUserOutOfRange(TestClient speakClient, string testMessage, ref string receivedMessage) + { + receivedMessage = ""; + + speakClient.Chat(0, ChatTypeEnum.Say, testMessage); + + Assert.AreNotEqual(testMessage, receivedMessage); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Friends/Tests/FriendModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Friends/Tests/FriendModuleTests.cs new file mode 100644 index 00000000000..79fcbc9ff0f --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Friends/Tests/FriendModuleTests.cs @@ -0,0 +1,204 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Friends.Tests +{ + [TestFixture] + public class FriendsModuleTests : OpenSimTestCase + { + private FriendsModule m_fm; + private TestScene m_scene; + + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public void Init() + { + // We must clear friends data between tests since Data.Null holds it in static properties. This is necessary + // so that different services and simulator can share the data in standalone mode. This is pretty horrible + // effectively the statics are global variables. + NullFriendsData.Clear(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + // Not strictly necessary since FriendsModule assumes it is the default (!) + config.Configs["Modules"].Set("FriendsModule", "FriendsModule"); + config.AddConfig("Friends"); + config.Configs["Friends"].Set("Connector", "OpenSim.Services.FriendsService.dll"); + config.AddConfig("FriendsService"); + config.Configs["FriendsService"].Set("StorageProvider", "OpenSim.Data.Null.dll"); + + m_scene = new SceneHelpers().SetupScene(); + m_fm = new FriendsModule(); + SceneHelpers.SetupSceneModules(m_scene, config, m_fm); + } + + [Test] + public void TestLoginWithNoFriends() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + + Assert.That(((TestClient)sp.ControllingClient).ReceivedOfflineNotifications.Count, Is.EqualTo(0)); + Assert.That(((TestClient)sp.ControllingClient).ReceivedOnlineNotifications.Count, Is.EqualTo(0)); + } + + [Test] + public void TestLoginWithOfflineFriends() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + +// UserAccountHelpers.CreateUserWithInventory(m_scene, user1Id); +// UserAccountHelpers.CreateUserWithInventory(m_scene, user2Id); +// +// m_fm.AddFriendship(user1Id, user2Id); + + ScenePresence sp1 = SceneHelpers.AddScenePresence(m_scene, user1Id); + ScenePresence sp2 = SceneHelpers.AddScenePresence(m_scene, user2Id); + + m_fm.AddFriendship(sp1.ControllingClient, user2Id); + + // Not necessary for this test. CanSeeOnline is automatically granted. +// m_fm.GrantRights(sp1.ControllingClient, user2Id, (int)FriendRights.CanSeeOnline); + + // We must logout from the client end so that the presence service is correctly updated by the presence + // detector. This is listening to the OnConnectionClosed event on the client. + ((TestClient)sp1.ControllingClient).Logout(); + ((TestClient)sp2.ControllingClient).Logout(); +// m_scene.RemoveClient(sp1.UUID, true); +// m_scene.RemoveClient(sp2.UUID, true); + + ScenePresence sp1Redux = SceneHelpers.AddScenePresence(m_scene, user1Id); + + // We don't expect to receive notifications of offline friends on login, just online. + Assert.That(((TestClient)sp1Redux.ControllingClient).ReceivedOfflineNotifications.Count, Is.EqualTo(0)); + Assert.That(((TestClient)sp1Redux.ControllingClient).ReceivedOnlineNotifications.Count, Is.EqualTo(0)); + } + + [Test] + public void TestLoginWithOnlineFriends() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + +// UserAccountHelpers.CreateUserWithInventory(m_scene, user1Id); +// UserAccountHelpers.CreateUserWithInventory(m_scene, user2Id); +// +// m_fm.AddFriendship(user1Id, user2Id); + + ScenePresence sp1 = SceneHelpers.AddScenePresence(m_scene, user1Id); + ScenePresence sp2 = SceneHelpers.AddScenePresence(m_scene, user2Id); + + m_fm.AddFriendship(sp1.ControllingClient, user2Id); + + // Not necessary for this test. CanSeeOnline is automatically granted. +// m_fm.GrantRights(sp1.ControllingClient, user2Id, (int)FriendRights.CanSeeOnline); + + // We must logout from the client end so that the presence service is correctly updated by the presence + // detector. This is listening to the OnConnectionClosed event on the client. +// ((TestClient)sp1.ControllingClient).Logout(); + ((TestClient)sp2.ControllingClient).Logout(); +// m_scene.RemoveClient(user2Id, true); + + ScenePresence sp2Redux = SceneHelpers.AddScenePresence(m_scene, user2Id); + + Assert.That(((TestClient)sp2Redux.ControllingClient).ReceivedOfflineNotifications.Count, Is.EqualTo(0)); + Assert.That(((TestClient)sp2Redux.ControllingClient).ReceivedOnlineNotifications.Count, Is.EqualTo(1)); + } + + [Test] + public void TestAddFriendshipWhileOnline() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + SceneHelpers.AddScenePresence(m_scene, user2Id); + + // This fiendship is two-way but without a connector, only the first user will receive the online + // notification. + m_fm.AddFriendship(sp.ControllingClient, user2Id); + + Assert.That(((TestClient)sp.ControllingClient).ReceivedOfflineNotifications.Count, Is.EqualTo(0)); + Assert.That(((TestClient)sp.ControllingClient).ReceivedOnlineNotifications.Count, Is.EqualTo(1)); + } + + [Test] + public void TestRemoveFriendshipWhileOnline() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, user1Id); + SceneHelpers.AddScenePresence(m_scene, user2Id); + + m_fm.AddFriendship(sp.ControllingClient, user2Id); + m_fm.RemoveFriendship(sp.ControllingClient, user2Id); + + TestClient user1Client = sp.ControllingClient as TestClient; + Assert.That(user1Client.ReceivedFriendshipTerminations.Count, Is.EqualTo(1)); + Assert.That(user1Client.ReceivedFriendshipTerminations[0], Is.EqualTo(user2Id)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs new file mode 100644 index 00000000000..1350e25f8e4 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadPathTests.cs @@ -0,0 +1,351 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveLoadPathTests : InventoryArchiveTestCase + { + /// + /// Test loading an IAR to various different inventory paths. + /// + [Test] + public void TestLoadIarToInventoryPaths() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SerialiserModule serialiserModule = new SerialiserModule(); + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + + // Annoyingly, we have to set up a scene even though inventory loading has nothing to do with a scene + Scene scene = new SceneHelpers().SetupScene(); + + SceneHelpers.SetupSceneModules(scene, serialiserModule, archiverModule); + + UserAccountHelpers.CreateUserWithInventory(scene, m_uaMT, "meowfood"); + UserAccountHelpers.CreateUserWithInventory(scene, m_uaLL1, "hampshire"); + + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "/", "meowfood", m_iarStream); + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); + + // Now try loading to a root child folder + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, m_uaMT.PrincipalID, "xA", false); + MemoryStream archiveReadStream = new MemoryStream(m_iarStream.ToArray()); + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "xA", "meowfood", archiveReadStream); + + InventoryItemBase foundItem2 + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, "xA/" + m_item1Name); + Assert.That(foundItem2, Is.Not.Null, "Didn't find loaded item 2"); + + // Now try loading to a more deeply nested folder + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, m_uaMT.PrincipalID, "xB/xC", false); + archiveReadStream = new MemoryStream(archiveReadStream.ToArray()); + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "xB/xC", "meowfood", archiveReadStream); + + InventoryItemBase foundItem3 + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, m_uaMT.PrincipalID, "xB/xC/" + m_item1Name); + Assert.That(foundItem3, Is.Not.Null, "Didn't find loaded item 3"); + } + + /// + /// Test that things work when the load path specified starts with a slash + /// + [Test] + public void TestLoadIarPathStartsWithSlash() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SerialiserModule serialiserModule = new SerialiserModule(); + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, serialiserModule, archiverModule); + + UserAccountHelpers.CreateUserWithInventory(scene, m_uaMT, "password"); + archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "/Objects", "password", m_iarStream); + + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath( + scene.InventoryService, m_uaMT.PrincipalID, "/Objects/" + m_item1Name); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1 in TestLoadIarFolderStartsWithSlash()"); + } + + [Test] + public void TestLoadIarPathWithEscapedChars() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string itemName = "You & you are a mean/man/"; + string humanEscapedItemName = @"You & you are a mean\/man\/"; + string userPassword = "meowfood"; + + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, archiverModule); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + UserAccountHelpers.CreateUserWithInventory(scene, userFirstName, userLastName, userId, "meowfood"); + + // Create asset + SceneObjectGroup object1; + SceneObjectPart part1; + { + string partName = "part name"; + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); + Vector3 groupPosition = new Vector3(10, 20, 30); + Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); + Vector3 offsetPosition = new Vector3(5, 10, 15); + + part1 + = new SceneObjectPart( + ownerId, shape, groupPosition, rotationOffset, offsetPosition); + part1.Name = partName; + + object1 = new SceneObjectGroup(part1); + scene.AddNewSceneObject(object1, false); + } + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = itemName; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + item1.Owner = userId; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + scene.AddInventoryItem(item1); + + MemoryStream archiveWriteStream = new MemoryStream(); + archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "Objects", userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + // LOAD ITEM + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + archiverModule.DearchiveInventory(UUID.Random(), userFirstName, userLastName, "Scripts", userPassword, archiveReadStream); + + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath( + scene.InventoryService, userId, "Scripts/Objects/" + humanEscapedItemName); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); +// Assert.That( +// foundItem1.CreatorId, Is.EqualTo(userUuid), +// "Loaded item non-uuid creator doesn't match that of the loading user"); + Assert.That( + foundItem1.Name, Is.EqualTo(itemName), + "Loaded item name doesn't match saved name"); + } + + /// + /// Test replication of an archive path to the user's inventory. + /// + [Test] + public void TestNewIarPath() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); + + Dictionary foldersCreated = new Dictionary(); + Dictionary nodesLoaded = new Dictionary(); + + string folder1Name = "1"; + string folder2aName = "2a"; + string folder2bName = "2b"; + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1Name, UUID.Random()); + string folder2aArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2aName, UUID.Random()); + string folder2bArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2bName, UUID.Random()); + + string iarPath1 = string.Join("", new string[] { folder1ArchiveName, folder2aArchiveName }); + string iarPath2 = string.Join("", new string[] { folder1ArchiveName, folder2bArchiveName }); + + { + // Test replication of path1 + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, null, (Stream)null, false) + .ReplicateArchivePathToUserInventory( + iarPath1, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + foldersCreated, nodesLoaded); + + List folder1Candidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1Name); + Assert.That(folder1Candidates.Count, Is.EqualTo(1)); + + InventoryFolderBase folder1 = folder1Candidates[0]; + List folder2aCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1, folder2aName); + Assert.That(folder2aCandidates.Count, Is.EqualTo(1)); + } + + { + // Test replication of path2 + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, null, (Stream)null, false) + .ReplicateArchivePathToUserInventory( + iarPath2, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + foldersCreated, nodesLoaded); + + List folder1Candidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1Name); + Assert.That(folder1Candidates.Count, Is.EqualTo(1)); + + InventoryFolderBase folder1 = folder1Candidates[0]; + + List folder2aCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1, folder2aName); + Assert.That(folder2aCandidates.Count, Is.EqualTo(1)); + + List folder2bCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1, folder2bName); + Assert.That(folder2bCandidates.Count, Is.EqualTo(1)); + } + } + + /// + /// Test replication of a partly existing archive path to the user's inventory. This should create + /// a duplicate path without the merge option. + /// + [Test] + public void TestPartExistingIarPath() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); + + string folder1ExistingName = "a"; + string folder2Name = "b"; + + InventoryFolderBase folder1 + = UserInventoryHelpers.CreateInventoryFolder( + scene.InventoryService, ua1.PrincipalID, folder1ExistingName, false); + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); + string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); + + string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); + + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, null, (Stream)null, false) + .ReplicateArchivePathToUserInventory( + itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + new Dictionary(), new Dictionary()); + + List folder1PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); + Assert.That(folder1PostCandidates.Count, Is.EqualTo(2)); + + // FIXME: Temporarily, we're going to do something messy to make sure we pick up the created folder. + InventoryFolderBase folder1Post = null; + foreach (InventoryFolderBase folder in folder1PostCandidates) + { + if (folder.ID != folder1.ID) + { + folder1Post = folder; + break; + } + } +// Assert.That(folder1Post.ID, Is.EqualTo(folder1.ID)); + + List folder2PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1Post, "b"); + Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); + } + + /// + /// Test replication of a partly existing archive path to the user's inventory. This should create + /// a merged path. + /// + [Test] + public void TestMergeIarPath() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(scene); + + string folder1ExistingName = "a"; + string folder2Name = "b"; + + InventoryFolderBase folder1 + = UserInventoryHelpers.CreateInventoryFolder( + scene.InventoryService, ua1.PrincipalID, folder1ExistingName, false); + + string folder1ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder1ExistingName, UUID.Random()); + string folder2ArchiveName = InventoryArchiveWriteRequest.CreateArchiveFolderName(folder2Name, UUID.Random()); + + string itemArchivePath = string.Join("", new string[] { folder1ArchiveName, folder2ArchiveName }); + + new InventoryArchiveReadRequest(UUID.Random(), null, scene.InventoryService, scene.AssetService, scene.UserAccountService, ua1, folder1ExistingName, (Stream)null, true) + .ReplicateArchivePathToUserInventory( + itemArchivePath, scene.InventoryService.GetRootFolder(ua1.PrincipalID), + new Dictionary(), new Dictionary()); + + List folder1PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, ua1.PrincipalID, folder1ExistingName); + Assert.That(folder1PostCandidates.Count, Is.EqualTo(1)); + Assert.That(folder1PostCandidates[0].ID, Is.EqualTo(folder1.ID)); + + List folder2PostCandidates + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, folder1PostCandidates[0], "b"); + Assert.That(folder2PostCandidates.Count, Is.EqualTo(1)); + } + } +} + diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs new file mode 100644 index 00000000000..e2852ec353b --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveLoadTests.cs @@ -0,0 +1,182 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveLoadTests : InventoryArchiveTestCase + { + protected TestScene m_scene; + protected InventoryArchiverModule m_archiverModule; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + SerialiserModule serialiserModule = new SerialiserModule(); + m_archiverModule = new InventoryArchiverModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, serialiserModule, m_archiverModule); + } + + [Test] + public void TestLoadCoalesecedItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL1, "password"); + m_archiverModule.DearchiveInventory(UUID.Random(), m_uaLL1.FirstName, m_uaLL1.LastName, "/", "password", m_iarStream); + + InventoryItemBase coaItem + = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaLL1.PrincipalID, m_coaItemName); + + Assert.That(coaItem, Is.Not.Null, "Didn't find loaded item 1"); + + string assetXml = AssetHelpers.ReadAssetAsString(m_scene.AssetService, coaItem.AssetID); + + CoalescedSceneObjects coa; + bool readResult = CoalescedSceneObjectsSerializer.TryFromXml(assetXml, out coa); + + Assert.That(readResult, Is.True); + Assert.That(coa.Count, Is.EqualTo(2)); + + List coaObjects = coa.Objects; + Assert.That(coaObjects[0].UUID, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000120"))); + Assert.That(coaObjects[0].AbsolutePosition, Is.EqualTo(new Vector3(15, 30, 45))); + + Assert.That(coaObjects[1].UUID, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000140"))); + Assert.That(coaObjects[1].AbsolutePosition, Is.EqualTo(new Vector3(25, 50, 75))); + } + + /// + /// Test case where a creator account exists for the creator UUID embedded in item metadata and serialized + /// objects. + /// + [Test] + public void TestLoadIarCreatorAccountPresent() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL1, "meowfood"); + + m_archiverModule.DearchiveInventory(UUID.Random(), m_uaLL1.FirstName, m_uaLL1.LastName, "/", "meowfood", m_iarStream); + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaLL1.PrincipalID, m_item1Name); + + Assert.That( + foundItem1.CreatorId, Is.EqualTo(m_uaLL1.PrincipalID.ToString()), + "Loaded item non-uuid creator doesn't match original"); + Assert.That( + foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaLL1.PrincipalID), + "Loaded item uuid creator doesn't match original"); + Assert.That(foundItem1.Owner, Is.EqualTo(m_uaLL1.PrincipalID), + "Loaded item owner doesn't match inventory reciever"); + + AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); + string xmlData = Utils.BytesToString(asset1.Data); + SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); + + Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaLL1.PrincipalID)); + } + +// /// +// /// Test loading a V0.1 OpenSim Inventory Archive (subject to change since there is no fixed format yet) where +// /// an account exists with the same name as the creator, though not the same id. +// /// +// [Test] +// public void TestLoadIarV0_1SameNameCreator() +// { +// TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); +// +// UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaMT, "meowfood"); +// UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaLL2, "hampshire"); +// +// m_archiverModule.DearchiveInventory(m_uaMT.FirstName, m_uaMT.LastName, "/", "meowfood", m_iarStream); +// InventoryItemBase foundItem1 +// = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); +// +// Assert.That( +// foundItem1.CreatorId, Is.EqualTo(m_uaLL2.PrincipalID.ToString()), +// "Loaded item non-uuid creator doesn't match original"); +// Assert.That( +// foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaLL2.PrincipalID), +// "Loaded item uuid creator doesn't match original"); +// Assert.That(foundItem1.Owner, Is.EqualTo(m_uaMT.PrincipalID), +// "Loaded item owner doesn't match inventory reciever"); +// +// AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); +// string xmlData = Utils.BytesToString(asset1.Data); +// SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); +// +// Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaLL2.PrincipalID)); +// } + + /// + /// Test loading a V0.1 OpenSim Inventory Archive (subject to change since there is no fixed format yet) where + /// the creator or an account with the creator's name does not exist within the system. + /// + [Test] + public void TestLoadIarV0_1AbsentCreator() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UserAccountHelpers.CreateUserWithInventory(m_scene, m_uaMT, "password"); + m_archiverModule.DearchiveInventory(UUID.Random(), m_uaMT.FirstName, m_uaMT.LastName, "/", "password", m_iarStream); + + InventoryItemBase foundItem1 + = InventoryArchiveUtils.FindItemByPath(m_scene.InventoryService, m_uaMT.PrincipalID, m_item1Name); + + Assert.That(foundItem1, Is.Not.Null, "Didn't find loaded item 1"); + Assert.That( + foundItem1.CreatorId, Is.EqualTo(m_uaMT.PrincipalID.ToString()), + "Loaded item non-uuid creator doesn't match that of the loading user"); + Assert.That( + foundItem1.CreatorIdAsUuid, Is.EqualTo(m_uaMT.PrincipalID), + "Loaded item uuid creator doesn't match that of the loading user"); + + AssetBase asset1 = m_scene.AssetService.Get(foundItem1.AssetID.ToString()); + string xmlData = Utils.BytesToString(asset1.Data); + SceneObjectGroup sog1 = SceneObjectSerializer.FromOriginalXmlFormat(xmlData); + + Assert.That(sog1.RootPart.CreatorID, Is.EqualTo(m_uaMT.PrincipalID)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs new file mode 100644 index 00000000000..a5c3c0c26b8 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveSaveTests.cs @@ -0,0 +1,412 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Framework.Serialization; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveSaveTests : InventoryArchiveTestCase + { + protected TestScene m_scene; + protected InventoryArchiverModule m_archiverModule; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + SerialiserModule serialiserModule = new SerialiserModule(); + m_archiverModule = new InventoryArchiverModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, serialiserModule, m_archiverModule); + } + + /// + /// Test that the IAR has the required files in the right order. + /// + /// + /// At the moment, the only thing that matters is that the control file is the very first one. + /// + [Test] + public void TestOrder() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + MemoryStream archiveReadStream = new MemoryStream(m_iarStreamBytes); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + byte[] data = tar.ReadEntry(out filePath, out tarEntryType); + Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); + + InventoryArchiveReadRequest iarr + = new InventoryArchiveReadRequest(UUID.Random(), null, null, null, null, null, null, (Stream)null, false); + iarr.LoadControlFile(filePath, data); + + Assert.That(iarr.ControlFileLoaded, Is.True); + } + + [Test] + public void TestSaveRootFolderToIar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = TestHelpers.ParseTail(0x20); + + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "/", userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + // Test created iar + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + +// InventoryArchiveUtils. + bool gotObjectsFolder = false; + + string objectsFolderName + = string.Format( + "{0}{1}", + ArchiveConstants.INVENTORY_PATH, + InventoryArchiveWriteRequest.CreateArchiveFolderName( + UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, userId, "Objects"))); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { +// Console.WriteLine("Got {0}", filePath); + + // Lazily, we only bother to look for the system objects folder created when we call CreateUserWithInventory() + // XXX: But really we need to stop all that stuff being created in tests or check for such folders + // more thoroughly + if (filePath == objectsFolderName) + gotObjectsFolder = true; + } + + Assert.That(gotObjectsFolder, Is.True); + } + + [Test] + public void TestSaveNonRootFolderToIar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = TestHelpers.ParseTail(0x20); + + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + // Create base folder + InventoryFolderBase f1 + = UserInventoryHelpers.CreateInventoryFolder(m_scene.InventoryService, userId, "f1", true); + + // Create item1 + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(1, userId, "My Little Dog Object", 0x5); + InventoryItemBase i1 = UserInventoryHelpers.AddInventoryItem(m_scene, so1, 0x50, 0x60, "f1"); + + // Create embedded folder + InventoryFolderBase f1_1 + = UserInventoryHelpers.CreateInventoryFolder(m_scene.InventoryService, userId, "f1/f1.1", true); + + // Create embedded item + SceneObjectGroup so1_1 = SceneHelpers.CreateSceneObject(1, userId, "My Little Cat Object", 0x6); + InventoryItemBase i2 = UserInventoryHelpers.AddInventoryItem(m_scene, so1_1, 0x500, 0x600, "f1/f1.1"); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "f1", userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + // Test created iar + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + +// InventoryArchiveUtils. + bool gotf1 = false, gotf1_1 = false, gotso1 = false, gotso2 = false; + + string f1FileName + = string.Format("{0}{1}", ArchiveConstants.INVENTORY_PATH, InventoryArchiveWriteRequest.CreateArchiveFolderName(f1)); + string f1_1FileName + = string.Format("{0}{1}", f1FileName, InventoryArchiveWriteRequest.CreateArchiveFolderName(f1_1)); + string so1FileName + = string.Format("{0}{1}", f1FileName, InventoryArchiveWriteRequest.CreateArchiveItemName(i1)); + string so2FileName + = string.Format("{0}{1}", f1_1FileName, InventoryArchiveWriteRequest.CreateArchiveItemName(i2)); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { +// Console.WriteLine("Got {0}", filePath); + + if (filePath == f1FileName) + gotf1 = true; + else if (filePath == f1_1FileName) + gotf1_1 = true; + else if (filePath == so1FileName) + gotso1 = true; + else if (filePath == so2FileName) + gotso2 = true; + } + +// Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotf1, Is.True); + Assert.That(gotf1_1, Is.True); + Assert.That(gotso1, Is.True); + Assert.That(gotso2, Is.True); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test saving a single inventory item to an IAR + /// (subject to change since there is no fixed format yet). + /// + [Test] + public void TestSaveItemToIar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + // Create asset + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "My Little Dog Object", 0x50); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + m_scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + string item1Name = "My Little Dog"; + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = item1Name; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + m_scene.AddInventoryItem(item1); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_archiverModule.OnInventoryArchiveSaved += SaveCompleted; + + mre.Reset(); + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "Objects/" + item1Name, userPassword, archiveWriteStream); + mre.WaitOne(60000, false); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + //bool gotControlFile = false; + bool gotObject1File = false; + //bool gotObject2File = false; + string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); + string expectedObject1FilePath = string.Format( + "{0}{1}", + ArchiveConstants.INVENTORY_PATH, + expectedObject1FileName); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + +// Console.WriteLine("Reading archive"); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + Console.WriteLine("Got {0}", filePath); + +// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) +// { +// gotControlFile = true; +// } + + if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) + { +// string fileName = filePath.Remove(0, "Objects/".Length); +// +// if (fileName.StartsWith(part1.Name)) +// { + Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); + gotObject1File = true; +// } +// else if (fileName.StartsWith(part2.Name)) +// { +// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); +// gotObject2File = true; +// } + } + } + +// Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotObject1File, Is.True, "No item1 file in archive"); +// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test saving a single inventory item to an IAR without its asset + /// + [Test] + public void TestSaveItemToIarNoAssets() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, userId, userPassword); + + // Create asset + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "My Little Dog Object", 0x50); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + m_scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + string item1Name = "My Little Dog"; + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = item1Name; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + m_scene.AddInventoryItem(item1); + + MemoryStream archiveWriteStream = new MemoryStream(); + + Dictionary options = new Dictionary(); + options.Add("noassets", true); + + // When we're not saving assets, archiving is being done synchronously. + m_archiverModule.ArchiveInventory( + UUID.Random(), userFirstName, userLastName, "Objects/" + item1Name, userPassword, archiveWriteStream, options); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + //bool gotControlFile = false; + bool gotObject1File = false; + //bool gotObject2File = false; + string expectedObject1FileName = InventoryArchiveWriteRequest.CreateArchiveItemName(item1); + string expectedObject1FilePath = string.Format( + "{0}{1}", + ArchiveConstants.INVENTORY_PATH, + expectedObject1FileName); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + +// Console.WriteLine("Reading archive"); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + Console.WriteLine("Got {0}", filePath); + +// if (ArchiveConstants.CONTROL_FILE_PATH == filePath) +// { +// gotControlFile = true; +// } + + if (filePath.StartsWith(ArchiveConstants.INVENTORY_PATH) && filePath.EndsWith(".xml")) + { +// string fileName = filePath.Remove(0, "Objects/".Length); +// +// if (fileName.StartsWith(part1.Name)) +// { + Assert.That(expectedObject1FilePath, Is.EqualTo(filePath)); + gotObject1File = true; +// } +// else if (fileName.StartsWith(part2.Name)) +// { +// Assert.That(fileName, Is.EqualTo(expectedObject2FileName)); +// gotObject2File = true; +// } + } + else if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + Assert.Fail("Found asset path in TestSaveItemToIarNoAssets()"); + } + } + +// Assert.That(gotControlFile, Is.True, "No control file in archive"); + Assert.That(gotObject1File, Is.True, "No item1 file in archive"); +// Assert.That(gotObject2File, Is.True, "No object2 file in archive"); + + // TODO: Test presence of more files and contents of files. + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs new file mode 100644 index 00000000000..db850de97d9 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Archiver/Tests/InventoryArchiveTestCase.cs @@ -0,0 +1,167 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Archiver.Tests +{ + [TestFixture] + public class InventoryArchiveTestCase : OpenSimTestCase + { + protected ManualResetEvent mre = new ManualResetEvent(false); + + /// + /// A raw array of bytes that we'll use to create an IAR memory stream suitable for isolated use in each test. + /// + protected byte[] m_iarStreamBytes; + + /// + /// Stream of data representing a common IAR for load tests. + /// + protected MemoryStream m_iarStream; + + protected UserAccount m_uaMT + = new UserAccount { + PrincipalID = UUID.Parse("00000000-0000-0000-0000-000000000555"), + FirstName = "Mr", + LastName = "Tiddles" }; + + protected UserAccount m_uaLL1 + = new UserAccount { + PrincipalID = UUID.Parse("00000000-0000-0000-0000-000000000666"), + FirstName = "Lord", + LastName = "Lucan" }; + + protected UserAccount m_uaLL2 + = new UserAccount { + PrincipalID = UUID.Parse("00000000-0000-0000-0000-000000000777"), + FirstName = "Lord", + LastName = "Lucan" }; + + protected string m_item1Name = "Ray Gun Item"; + protected string m_coaItemName = "Coalesced Item"; + + [OneTimeSetUp] + public void FixtureSetup() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + + ConstructDefaultIarBytesForTestLoad(); + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + m_iarStream = new MemoryStream(m_iarStreamBytes); + } + + protected void ConstructDefaultIarBytesForTestLoad() + { + + InventoryArchiverModule archiverModule = new InventoryArchiverModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, archiverModule); + + UserAccountHelpers.CreateUserWithInventory(scene, m_uaLL1, "hampshire"); + + MemoryStream archiveWriteStream = new MemoryStream(); + + //InventoryFolderBase objects = scene.InventoryService.GetFolderForType(m_uaLL1.PrincipalID, FolderType.Object); + // Create scene object asset + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000040"); + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, ownerId, "Ray Gun Object", 0x50); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + scene.AssetService.Store(asset1); + + // Create scene object item + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = m_item1Name; + item1.ID = UUID.Parse("00000000-0000-0000-0000-000000000020"); + item1.AssetID = asset1.FullID; + item1.GroupID = UUID.Random(); + item1.CreatorId = m_uaLL1.PrincipalID.ToString(); + item1.Owner = m_uaLL1.PrincipalID; + //item1.Folder = objects.ID; + item1.Folder = scene.InventoryService.GetRootFolder(m_uaLL1.PrincipalID).ID; + scene.AddInventoryItem(item1); + + // Create coalesced objects asset + SceneObjectGroup cobj1 = SceneHelpers.CreateSceneObject(1, m_uaLL1.PrincipalID, "Object1", 0x120); + cobj1.AbsolutePosition = new Vector3(15, 30, 45); + + SceneObjectGroup cobj2 = SceneHelpers.CreateSceneObject(1, m_uaLL1.PrincipalID, "Object2", 0x140); + cobj2.AbsolutePosition = new Vector3(25, 50, 75); + + CoalescedSceneObjects coa = new CoalescedSceneObjects(m_uaLL1.PrincipalID, cobj1, cobj2); + + AssetBase coaAsset = AssetHelpers.CreateAsset(0x160, coa); + scene.AssetService.Store(coaAsset); + + // Create coalesced objects inventory item + InventoryItemBase coaItem = new InventoryItemBase(); + coaItem.Name = m_coaItemName; + coaItem.ID = UUID.Parse("00000000-0000-0000-0000-000000000180"); + coaItem.AssetID = coaAsset.FullID; + coaItem.GroupID = UUID.Random(); + coaItem.CreatorId = m_uaLL1.PrincipalID.ToString(); + coaItem.Owner = m_uaLL1.PrincipalID; + //coaItem.Folder = objects.ID; + coaItem.Folder = scene.InventoryService.GetRootFolder(m_uaLL1.PrincipalID).ID; + scene.AddInventoryItem(coaItem); + + archiverModule.ArchiveInventory( + UUID.Random(), m_uaLL1.FirstName, m_uaLL1.LastName, "/*", "hampshire", archiveWriteStream); + + m_iarStreamBytes = archiveWriteStream.ToArray(); + } + + protected void SaveCompleted( + UUID id, bool succeeded, UserAccount userInfo, string invPath, Stream saveStream, + Exception reportedException, int SaveCount, int FilterCount) + { + mre.Set(); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs new file mode 100644 index 00000000000..3ad935cc023 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Avatar/Inventory/Transfer/Tests/InventoryTransferModuleTests.cs @@ -0,0 +1,440 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Avatar.Inventory.Transfer.Tests +{ + [TestFixture] + public class InventoryTransferModuleTests : OpenSimTestCase + { + protected TestScene m_scene; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Messaging"); + config.Configs["Messaging"].Set("InventoryTransferModule", "InventoryTransferModule"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, config, new InventoryTransferModule()); + } + + [Test] + public void TestAcceptGivenItem() + { +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID itemId = TestHelpers.ParseTail(0x100); + UUID assetId = TestHelpers.ParseTail(0x200); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + // Create the object to test give + InventoryItemBase originalItem + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "givenObj", itemId, assetId, giverSp.UUID, InventoryType.Object); + + byte[] giveImBinaryBucket = new byte[17]; + byte[] itemIdBytes = itemId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + GridInstantMessage acceptIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryAccepted, + false, + "inventory accepted msg", + initialSessionId, + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(acceptIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryItemBase originalItemAfterGive + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterGive, Is.Not.Null); + Assert.That(originalItemAfterGive.ID, Is.EqualTo(originalItem.ID)); + + // Test for item successfully making it into the receiver's inventory + InventoryItemBase receivedItem + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, receiverSp.UUID, "Objects/givenObj"); + + Assert.That(receivedItem, Is.Not.Null); + Assert.That(receivedItem.ID, Is.Not.EqualTo(originalItem.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.DeleteItems(receiverSp.UUID, new List() { receivedItem.ID }); + + InventoryItemBase originalItemAfterDelete + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterDelete, Is.Not.Null); + + // TODO: Test scenario where giver deletes their item first. + } + + /// + /// Test user rejection of a given item. + /// + /// + /// A rejected item still ends up in the user's trash folder. + /// + [Test] + public void TestRejectGivenItem() + { +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID itemId = TestHelpers.ParseTail(0x100); + UUID assetId = TestHelpers.ParseTail(0x200); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + // Create the object to test give + InventoryItemBase originalItem + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "givenObj", itemId, assetId, giverSp.UUID, InventoryType.Object); + + GridInstantMessage receivedIm = null; + receiverClient.OnReceivedInstantMessage += im => receivedIm = im; + + byte[] giveImBinaryBucket = new byte[17]; + byte[] itemIdBytes = itemId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + // Session ID is now the created item ID (!) + GridInstantMessage rejectIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryDeclined, + false, + "inventory declined msg", + new UUID(receivedIm.imSessionID), + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(rejectIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryItemBase originalItemAfterGive + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterGive, Is.Not.Null); + Assert.That(originalItemAfterGive.ID, Is.EqualTo(originalItem.ID)); + + // Test for item successfully making it into the receiver's inventory + InventoryItemBase receivedItem + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, receiverSp.UUID, "Trash/givenObj"); + + InventoryFolderBase trashFolder + = m_scene.InventoryService.GetFolderForType(receiverSp.UUID, FolderType.Trash); + + Assert.That(receivedItem, Is.Not.Null); + Assert.That(receivedItem.ID, Is.Not.EqualTo(originalItem.ID)); + Assert.That(receivedItem.Folder, Is.EqualTo(trashFolder.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.PurgeFolder(trashFolder); + + InventoryItemBase originalItemAfterDelete + = UserInventoryHelpers.GetInventoryItem(m_scene.InventoryService, giverSp.UUID, "Objects/givenObj"); + + Assert.That(originalItemAfterDelete, Is.Not.Null); + } + + [Test] + public void TestAcceptGivenFolder() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID folderId = TestHelpers.ParseTail(0x100); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + InventoryFolderBase originalFolder + = UserInventoryHelpers.CreateInventoryFolder( + m_scene.InventoryService, giverSp.UUID, folderId, "f1", true); + + byte[] giveImBinaryBucket = new byte[17]; + giveImBinaryBucket[0] = (byte)AssetType.Folder; + byte[] itemIdBytes = folderId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + GridInstantMessage acceptIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryAccepted, + false, + "inventory accepted msg", + initialSessionId, + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(acceptIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryFolderBase originalFolderAfterGive + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterGive, Is.Not.Null); + Assert.That(originalFolderAfterGive.ID, Is.EqualTo(originalFolder.ID)); + + // Test for item successfully making it into the receiver's inventory + InventoryFolderBase receivedFolder + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, receiverSp.UUID, "f1"); + + Assert.That(receivedFolder, Is.Not.Null); + Assert.That(receivedFolder.ID, Is.Not.EqualTo(originalFolder.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.DeleteFolders(receiverSp.UUID, new List() { receivedFolder.ID }); + + InventoryFolderBase originalFolderAfterDelete + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterDelete, Is.Not.Null); + + // TODO: Test scenario where giver deletes their item first. + } + + /// + /// Test user rejection of a given item. + /// + /// + /// A rejected item still ends up in the user's trash folder. + /// + [Test] + public void TestRejectGivenFolder() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID initialSessionId = TestHelpers.ParseTail(0x10); + UUID folderId = TestHelpers.ParseTail(0x100); + + UserAccount ua1 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "One", TestHelpers.ParseTail(0x1), "pw"); + UserAccount ua2 + = UserAccountHelpers.CreateUserWithInventory(m_scene, "User", "Two", TestHelpers.ParseTail(0x2), "pw"); + + ScenePresence giverSp = SceneHelpers.AddScenePresence(m_scene, ua1); + TestClient giverClient = (TestClient)giverSp.ControllingClient; + + ScenePresence receiverSp = SceneHelpers.AddScenePresence(m_scene, ua2); + TestClient receiverClient = (TestClient)receiverSp.ControllingClient; + + // Create the folder to test give + InventoryFolderBase originalFolder + = UserInventoryHelpers.CreateInventoryFolder( + m_scene.InventoryService, giverSp.UUID, folderId, "f1", true); + + GridInstantMessage receivedIm = null; + receiverClient.OnReceivedInstantMessage += im => receivedIm = im; + + byte[] giveImBinaryBucket = new byte[17]; + giveImBinaryBucket[0] = (byte)AssetType.Folder; + byte[] itemIdBytes = folderId.GetBytes(); + Array.Copy(itemIdBytes, 0, giveImBinaryBucket, 1, itemIdBytes.Length); + + GridInstantMessage giveIm + = new GridInstantMessage( + m_scene, + giverSp.UUID, + giverSp.Name, + receiverSp.UUID, + (byte)InstantMessageDialog.InventoryOffered, + false, + "inventory offered msg", + initialSessionId, + false, + Vector3.Zero, + giveImBinaryBucket, + true); + + giverClient.HandleImprovedInstantMessage(giveIm); + + // These details might not all be correct. + // Session ID is now the created item ID (!) + GridInstantMessage rejectIm + = new GridInstantMessage( + m_scene, + receiverSp.UUID, + receiverSp.Name, + giverSp.UUID, + (byte)InstantMessageDialog.InventoryDeclined, + false, + "inventory declined msg", + new UUID(receivedIm.imSessionID), + false, + Vector3.Zero, + null, + true); + + receiverClient.HandleImprovedInstantMessage(rejectIm); + + // Test for item remaining in the giver's inventory (here we assume a copy item) + // TODO: Test no-copy items. + InventoryFolderBase originalFolderAfterGive + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterGive, Is.Not.Null); + Assert.That(originalFolderAfterGive.ID, Is.EqualTo(originalFolder.ID)); + + // Test for folder successfully making it into the receiver's inventory + InventoryFolderBase receivedFolder + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, receiverSp.UUID, "Trash/f1"); + + InventoryFolderBase trashFolder + = m_scene.InventoryService.GetFolderForType(receiverSp.UUID, FolderType.Trash); + + Assert.That(receivedFolder, Is.Not.Null); + Assert.That(receivedFolder.ID, Is.Not.EqualTo(originalFolder.ID)); + Assert.That(receivedFolder.ParentID, Is.EqualTo(trashFolder.ID)); + + // Test that on a delete, item still exists and is accessible for the giver. + m_scene.InventoryService.PurgeFolder(trashFolder); + + InventoryFolderBase originalFolderAfterDelete + = UserInventoryHelpers.GetInventoryFolder(m_scene.InventoryService, giverSp.UUID, "f1"); + + Assert.That(originalFolderAfterDelete, Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs new file mode 100644 index 00000000000..1b2700e32f2 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/HGAssetMapperTests.cs @@ -0,0 +1,147 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Threading; +using System.Xml; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.Framework.Scenes; +//using OpenSim.Region.ScriptEngine.XEngine; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests +{ + [TestFixture] + public class HGAssetMapperTests : OpenSimTestCase + { + /* + [Test] + public void TestPostAssetRewrite() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + XEngine xengine = new XEngine(); + xengine.DebugLevel = 1; + + IniConfigSource configSource = new IniConfigSource(); + + IConfig startupConfig = configSource.AddConfig("Startup"); + startupConfig.Set("DefaultScriptEngine", "XEngine"); + + IConfig xEngineConfig = configSource.AddConfig("XEngine"); + xEngineConfig.Set("Enabled", "true"); + xEngineConfig.Set("StartDelay", "0"); + xEngineConfig.Set("AppDomainLoading", "false"); + + string homeUrl = "http://hg.HomeTestPostAssetRewriteGrid.com"; + string foreignUrl = "http://hg.ForeignTestPostAssetRewriteGrid.com"; + int soIdTail = 0x1; + UUID assetId = TestHelpers.ParseTail(0x10); + UUID userId = TestHelpers.ParseTail(0x100); + UUID sceneId = TestHelpers.ParseTail(0x1000); + string userFirstName = "TestPostAsset"; + string userLastName = "Rewrite"; + int soPartsCount = 3; + + Scene scene = new SceneHelpers().SetupScene("TestPostAssetRewriteScene", sceneId, 1000, 1000, configSource); + SceneHelpers.SetupSceneModules(scene, configSource, xengine); + scene.StartScripts(); + + HGAssetMapper hgam = new HGAssetMapper(scene, homeUrl); + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, userFirstName, userLastName, userId, "password"); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, soPartsCount, ua.PrincipalID, "part", soIdTail); + RezScript( + scene, so.UUID, "default { state_entry() { llSay(0, \"Hello World\"); } }", "item1", ua.PrincipalID); + + AssetBase asset = AssetHelpers.CreateAsset(assetId, so); + asset.CreatorID = foreignUrl; + hgam.PostAsset(foreignUrl, asset); + + // Check transformed asset. + AssetBase ncAssetGet = scene.AssetService.Get(assetId.ToString()); + Assert.AreEqual(foreignUrl, ncAssetGet.CreatorID); + string xmlData = Utils.BytesToString(ncAssetGet.Data); + XmlDocument ncAssetGetXmlDoc = new XmlDocument(); + ncAssetGetXmlDoc.LoadXml(xmlData); + + // Console.WriteLine(ncAssetGetXmlDoc.OuterXml); + + XmlNodeList creatorDataNodes = ncAssetGetXmlDoc.GetElementsByTagName("CreatorData"); + + Assert.AreEqual(soPartsCount, creatorDataNodes.Count); + //Console.WriteLine("creatorDataNodes {0}", creatorDataNodes.Count); + + foreach (XmlNode creatorDataNode in creatorDataNodes) + { + Assert.AreEqual( + string.Format("{0};{1} {2}", homeUrl, ua.FirstName, ua.LastName), creatorDataNode.InnerText); + } + + // Check that saved script nodes have attributes + XmlNodeList savedScriptStateNodes = ncAssetGetXmlDoc.GetElementsByTagName("SavedScriptState"); + + Assert.AreEqual(1, savedScriptStateNodes.Count); + Assert.AreEqual(1, savedScriptStateNodes[0].Attributes.Count); + XmlNode uuidAttribute = savedScriptStateNodes[0].Attributes.GetNamedItem("UUID"); + Assert.NotNull(uuidAttribute); + // XXX: To check the actual UUID attribute we would have to do some work to retreive the UUID of the task + // item created earlier. + } + + private void RezScript(Scene scene, UUID soId, string script, string itemName, UUID userId) + { + InventoryItemBase itemTemplate = new InventoryItemBase(); + // itemTemplate.ID = itemId; + itemTemplate.Name = itemName; + itemTemplate.Folder = soId; + itemTemplate.InvType = (int)InventoryType.LSL; + + // XXX: Ultimately it would be better to be able to directly manipulate the script engine to rez a script + // immediately for tests rather than chunter through it's threaded mechanisms. + AutoResetEvent chatEvent = new AutoResetEvent(false); + + scene.EventManager.OnChatFromWorld += (s, c) => + { +// Console.WriteLine("Got chat [{0}]", c.Message); + chatEvent.Set(); + }; + + scene.RezNewScript(userId, itemTemplate, script); + +// Console.WriteLine("HERE"); + Assert.IsTrue(chatEvent.WaitOne(60000), "Chat event in HGAssetMapperTests.RezScript not received"); + } + */ + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs new file mode 100644 index 00000000000..3b3e34a3053 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Framework/InventoryAccess/Tests/InventoryAccessModuleTests.cs @@ -0,0 +1,166 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Framework.InventoryAccess.Tests +{ + [TestFixture] + public class InventoryAccessModuleTests : OpenSimTestCase + { + protected TestScene m_scene; + protected BasicInventoryAccessModule m_iam; + protected UUID m_userId = UUID.Parse("00000000-0000-0000-0000-000000000020"); + protected TestClient m_tc; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_iam = new BasicInventoryAccessModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + SceneHelpers sceneHelpers = new SceneHelpers(); + m_scene = sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, config, m_iam); + + // Create user + string userFirstName = "Jock"; + string userLastName = "Stirrup"; + string userPassword = "troll"; + UserAccountHelpers.CreateUserWithInventory(m_scene, userFirstName, userLastName, m_userId, userPassword); + + AgentCircuitData acd = new AgentCircuitData(); + acd.AgentID = m_userId; + m_tc = new TestClient(acd, m_scene); + } + + [Test] + public void TestRezCoalescedObject() + { +/* + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Create asset + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, m_userId, "Object1", 0x20); + object1.AbsolutePosition = new Vector3(15, 30, 45); + + SceneObjectGroup object2 = SceneHelpers.CreateSceneObject(1, m_userId, "Object2", 0x40); + object2.AbsolutePosition = new Vector3(25, 50, 75); + + CoalescedSceneObjects coa = new CoalescedSceneObjects(m_userId, object1, object2); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, coa); + m_scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + string item1Name = "My Little Dog"; + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = item1Name; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, m_userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + item1.Flags |= (uint)InventoryItemFlags.ObjectHasMultipleItems; + m_scene.AddInventoryItem(item1); + + SceneObjectGroup so + = m_iam.RezObject( + m_tc, item1Id, new Vector3(100, 100, 100), Vector3.Zero, UUID.Zero, 1, false, false, false, UUID.Zero, false); + + Assert.That(so, Is.Not.Null); + + Assert.That(m_scene.SceneGraph.GetTotalObjectsCount(), Is.EqualTo(2)); + + SceneObjectPart retrievedObj1Part = m_scene.GetSceneObjectPart(object1.Name); + Assert.That(retrievedObj1Part, Is.Null); + + retrievedObj1Part = m_scene.GetSceneObjectPart(item1.Name); + Assert.That(retrievedObj1Part, Is.Not.Null); + Assert.That(retrievedObj1Part.Name, Is.EqualTo(item1.Name)); + + // Bottom of coalescence is placed on ground, hence we end up with 100.5 rather than 85 since the bottom + // object is unit square. + Assert.That(retrievedObj1Part.AbsolutePosition, Is.EqualTo(new Vector3(95, 90, 100.5f))); + + SceneObjectPart retrievedObj2Part = m_scene.GetSceneObjectPart(object2.Name); + Assert.That(retrievedObj2Part, Is.Not.Null); + Assert.That(retrievedObj2Part.Name, Is.EqualTo(object2.Name)); + Assert.That(retrievedObj2Part.AbsolutePosition, Is.EqualTo(new Vector3(105, 110, 130.5f))); +*/ + } + + [Test] + public void TestRezObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Create asset + SceneObjectGroup object1 = SceneHelpers.CreateSceneObject(1, m_userId, "My Little Dog Object", 0x40); + + UUID asset1Id = UUID.Parse("00000000-0000-0000-0000-000000000060"); + AssetBase asset1 = AssetHelpers.CreateAsset(asset1Id, object1); + m_scene.AssetService.Store(asset1); + + // Create item + UUID item1Id = UUID.Parse("00000000-0000-0000-0000-000000000080"); + string item1Name = "My Little Dog"; + InventoryItemBase item1 = new InventoryItemBase(); + item1.Name = item1Name; + item1.AssetID = asset1.FullID; + item1.ID = item1Id; + item1.Owner = m_userId; + InventoryFolderBase objsFolder + = InventoryArchiveUtils.FindFoldersByPath(m_scene.InventoryService, m_userId, "Objects")[0]; + item1.Folder = objsFolder.ID; + m_scene.AddInventoryItem(item1); + + SceneObjectGroup so + = m_iam.RezObject( + m_tc, item1Id, UUID.Zero, Vector3.Zero, Vector3.Zero, UUID.Zero, 1, false, false, false, UUID.Zero, false); + + Assert.That(so, Is.Not.Null); + + SceneObjectPart retrievedPart = m_scene.GetSceneObjectPart(so.UUID); + Assert.That(retrievedPart, Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs new file mode 100644 index 00000000000..e99e682b58d --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Framework/UserManagement/Tests/HGUserManagementModuleTests.cs @@ -0,0 +1,74 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.UserManagement; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Framework.UserManagement.Tests +{ + [TestFixture] + public class HGUserManagementModuleTests : OpenSimTestCase + { + /// + /// Test that a new HG agent (i.e. one without a user account) has their name cached in the UMM upon creation. + /// + [Test] + public void TestCachedUserNameForNewAgent() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + HGUserManagementModule hgumm = new HGUserManagementModule(); + UUID userId = TestHelpers.ParseStem("11"); + string firstName = "Fred"; + string lastName = "Astaire"; + string homeUri = "example.com:8002"; + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("UserManagementModule", hgumm.Name); + + SceneHelpers sceneHelpers = new SceneHelpers(); + TestScene scene = sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(scene, config, hgumm); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + acd.firstname = firstName; + acd.lastname = lastName; + acd.ServiceURLs["HomeURI"] = "http://" + homeUri; + + SceneHelpers.AddScenePresence(scene, acd); + + string name = hgumm.GetUserName(userId); + Assert.That(name, Is.EqualTo(string.Format("{0}.{1} @{2}", firstName, lastName, homeUri))); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/OpenSim.Region.CoreModules.Tests.csproj b/Tests/OpenSim.Region.CoreModules.Tests/OpenSim.Region.CoreModules.Tests.csproj new file mode 100644 index 00000000000..985ba464e73 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/OpenSim.Region.CoreModules.Tests.csproj @@ -0,0 +1,66 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + ..\..\bin\XMLRPC.dll + False + + + + + + + + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs new file mode 100644 index 00000000000..4b7fead4388 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Scripting/HttpRequest/Tests/ScriptsHttpRequestsTests.cs @@ -0,0 +1,189 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Net; +using System.Runtime.Serialization; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Scripting.HttpRequest.Tests +{ + class TestWebRequestCreate : IWebRequestCreate + { + public TestWebRequest NextRequest { get; set; } + + public WebRequest Create(Uri uri) + { +// NextRequest.RequestUri = uri; + + return NextRequest; + +// return new TestWebRequest(new SerializationInfo(typeof(TestWebRequest), new FormatterConverter()), new StreamingContext()); + } + } + + class TestWebRequest : WebRequest + { + public override string ContentType { get; set; } + public override string Method { get; set; } + + public Func OnEndGetResponse { get; set; } + + public TestWebRequest() : base() + { +// Console.WriteLine("created"); + } + +// public TestWebRequest(SerializationInfo serializationInfo, StreamingContext streamingContext) +// : base(serializationInfo, streamingContext) +// { +// Console.WriteLine("created"); +// } + + public override IAsyncResult BeginGetResponse(AsyncCallback callback, object state) + { +// Console.WriteLine("bish"); + TestAsyncResult tasr = new TestAsyncResult(); + callback(tasr); + + return tasr; + } + + public override WebResponse EndGetResponse(IAsyncResult asyncResult) + { +// Console.WriteLine("bosh"); + return OnEndGetResponse(asyncResult); + } + } + + class TestHttpWebResponse : HttpWebResponse + { + public string Response { get; set; } + +#pragma warning disable 0618 + public TestHttpWebResponse(SerializationInfo serializationInfo, StreamingContext streamingContext) + : base(serializationInfo, streamingContext) {} +#pragma warning restore 0618 + + public override Stream GetResponseStream() + { + return new MemoryStream(Encoding.UTF8.GetBytes(Response)); + } + } + + class TestAsyncResult : IAsyncResult + { + WaitHandle m_wh = new ManualResetEvent(true); + + object IAsyncResult.AsyncState + { + get { + throw new System.NotImplementedException (); + } + } + + WaitHandle IAsyncResult.AsyncWaitHandle + { + get { return m_wh; } + } + + bool IAsyncResult.CompletedSynchronously + { + get { return false; } + } + + bool IAsyncResult.IsCompleted + { + get { return true; } + } + } + + /// + /// Test script http request code. + /// + /// + /// This class uses some very hacky workarounds in order to mock HttpWebResponse which are Mono dependent (though + /// alternative code can be written to make this work for Windows). However, the value of being able to + /// regression test this kind of code is very high. + /// + [TestFixture] + public class ScriptsHttpRequestsTests : OpenSimTestCase + { + /// + /// Test what happens when we get a 404 response from a call. + /// +// [Test] + public void Test404Response() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + if (!Util.IsPlatformMono) + Assert.Ignore("Ignoring test since can only currently run on Mono"); + + string rawResponse = "boom"; + + TestWebRequestCreate twrc = new TestWebRequestCreate(); + + TestWebRequest twr = new TestWebRequest(); + //twr.OnEndGetResponse += ar => new TestHttpWebResponse(null, new StreamingContext()); + twr.OnEndGetResponse += ar => + { + SerializationInfo si = new SerializationInfo(typeof(HttpWebResponse), new FormatterConverter()); + StreamingContext sc = new StreamingContext(); +// WebHeaderCollection headers = new WebHeaderCollection(); +// si.AddValue("m_HttpResponseHeaders", headers); + si.AddValue("uri", new Uri("test://arrg")); +// si.AddValue("m_Certificate", null); + si.AddValue("version", HttpVersion.Version11); + si.AddValue("statusCode", HttpStatusCode.NotFound); + si.AddValue("contentLength", 0); + si.AddValue("method", "GET"); + si.AddValue("statusDescription", "Not Found"); + si.AddValue("contentType", null); + si.AddValue("cookieCollection", new CookieCollection()); + + TestHttpWebResponse thwr = new TestHttpWebResponse(si, sc); + thwr.Response = rawResponse; + + throw new WebException("no message", null, WebExceptionStatus.ProtocolError, thwr); + }; + + twrc.NextRequest = twr; + + WebRequest.RegisterPrefix("test", twrc); + HttpRequestClass hr = new HttpRequestClass(); + hr.Url = "test://something"; + hr.SendRequest(); + + Assert.That(hr.Status, Is.EqualTo((int)HttpStatusCode.NotFound)); + Assert.That(hr.ResponseBody, Is.EqualTo(rawResponse)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs new file mode 100644 index 00000000000..3e57b89bba4 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/Scripting/VectorRender/Tests/VectorRenderModuleTests.cs @@ -0,0 +1,311 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Region.CoreModules.Scripting.DynamicTexture; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Scripting.VectorRender.Tests +{ + [TestFixture] + public class VectorRenderModuleTests : OpenSimTestCase + { + Scene m_scene; + DynamicTextureModule m_dtm; + VectorRenderModule m_vrm; + + private void SetupScene(bool reuseTextures) + { + + TestsAssetCache cache = new TestsAssetCache(); + m_scene = new SceneHelpers(cache).SetupScene(); + + m_dtm = new DynamicTextureModule(); + m_dtm.ReuseTextures = reuseTextures; +// m_dtm.ReuseLowDataTextures = reuseTextures; + + m_vrm = new VectorRenderModule(); + + SceneHelpers.SetupSceneModules(m_scene, m_dtm, m_vrm); + } + + [Test] + public void TestDraw() + { + TestHelpers.InMethod(); + + SetupScene(false); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + UUID originalTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;", + ""); + + Assert.That(originalTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestRepeatSameDraw() + { + TestHelpers.InMethod(); + + string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;"; + + SetupScene(false); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestRepeatSameDrawDifferentExtraParams() + { + TestHelpers.InMethod(); + + string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;"; + + SetupScene(false); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + "alpha:250"); + + Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestRepeatSameDrawContainingImage() + { + TestHelpers.InMethod(); + + string dtText + = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World; Image http://0.0.0.0/shouldnotexist.png"; + + SetupScene(false); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestDrawReusingTexture() + { + TestHelpers.InMethod(); + + SetupScene(true); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + UUID originalTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;", + ""); + + Assert.That(originalTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestRepeatSameDrawReusingTexture() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;"; + + SetupScene(true); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + Assert.That(firstDynamicTextureID, Is.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + /// + /// Test a low data dynamically generated texture such that it is treated as a low data texture that causes + /// problems for current viewers. + /// + /// + /// As we do not set DynamicTextureModule.ReuseLowDataTextures = true in this test, it should not reuse the + /// texture + /// + [Test] + public void TestRepeatSameDrawLowDataTexture() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;"; + + SetupScene(true); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + "1024"); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + "1024"); + + Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestRepeatSameDrawDifferentExtraParamsReusingTexture() + { + TestHelpers.InMethod(); + + string dtText = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World;"; + + SetupScene(true); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + "alpha:250"); + + Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + + [Test] + public void TestRepeatSameDrawContainingImageReusingTexture() + { + TestHelpers.InMethod(); + + string dtText + = "PenColour BLACK; MoveTo 40,220; FontSize 32; Text Hello World; Image http://0.0.0.0/shouldnotexist.png"; + + SetupScene(true); + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + UUID firstDynamicTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + m_dtm.AddDynamicTextureData( + m_scene.RegionInfo.RegionID, + so.UUID, + m_vrm.GetContentType(), + dtText, + ""); + + Assert.That(firstDynamicTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs new file mode 100644 index 00000000000..3990bfc1924 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Asset/Tests/AssetConnectorTests.cs @@ -0,0 +1,163 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset.Tests +{ + [TestFixture] + public class AssetConnectorTests : OpenSimTestCase + { + [Test] + public void TestAddAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetServices", "LocalAssetServicesConnector"); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("LocalServiceModule", "OpenSim.Services.AssetService.dll:AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); + lasc.Initialise(config); + + AssetBase a1 = AssetHelpers.CreateNotecardAsset(); + lasc.Store(a1); + + AssetBase retreivedA1 = lasc.Get(a1.ID); + Assert.That(retreivedA1.ID, Is.EqualTo(a1.ID)); + Assert.That(retreivedA1.Metadata.ID, Is.EqualTo(a1.Metadata.ID)); + Assert.That(retreivedA1.Data.Length, Is.EqualTo(a1.Data.Length)); + + AssetMetadata retrievedA1Metadata = lasc.GetMetadata(a1.ID); + Assert.That(retrievedA1Metadata.ID, Is.EqualTo(a1.ID)); + + byte[] retrievedA1Data = lasc.GetData(a1.ID); + Assert.That(retrievedA1Data.Length, Is.EqualTo(a1.Data.Length)); + + // TODO: Add cache and check that this does receive a copy of the asset + } + + public void TestAddTemporaryAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetServices", "LocalAssetServicesConnector"); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("LocalServiceModule", "OpenSim.Services.AssetService.dll:AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); + lasc.Initialise(config); + + // If it is remote, it should be stored + AssetBase a2 = AssetHelpers.CreateNotecardAsset(); + a2.Local = false; + a2.Temporary = true; + + lasc.Store(a2); + + AssetBase retreivedA2 = lasc.Get(a2.ID); + Assert.That(retreivedA2.ID, Is.EqualTo(a2.ID)); + Assert.That(retreivedA2.Metadata.ID, Is.EqualTo(a2.Metadata.ID)); + Assert.That(retreivedA2.Data.Length, Is.EqualTo(a2.Data.Length)); + + AssetMetadata retrievedA2Metadata = lasc.GetMetadata(a2.ID); + Assert.That(retrievedA2Metadata.ID, Is.EqualTo(a2.ID)); + + byte[] retrievedA2Data = lasc.GetData(a2.ID); + Assert.That(retrievedA2Data.Length, Is.EqualTo(a2.Data.Length)); + + // TODO: Add cache and check that this does receive a copy of the asset + } + + [Test] + public void TestAddLocalAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetServices", "LocalAssetServicesConnector"); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("LocalServiceModule", "OpenSim.Services.AssetService.dll:AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); + lasc.Initialise(config); + + AssetBase a1 = AssetHelpers.CreateNotecardAsset(); + a1.Local = true; + + lasc.Store(a1); + + Assert.That(lasc.Get(a1.ID), Is.Null); + Assert.That(lasc.GetData(a1.ID), Is.Null); + Assert.That(lasc.GetMetadata(a1.ID), Is.Null); + + // TODO: Add cache and check that this does receive a copy of the asset + } + + [Test] + public void TestAddTemporaryLocalAsset() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetServices", "LocalAssetServicesConnector"); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("LocalServiceModule", "OpenSim.Services.AssetService.dll:AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalAssetServicesConnector lasc = new LocalAssetServicesConnector(); + lasc.Initialise(config); + + // If it is local, it should not be stored + AssetBase a1 = AssetHelpers.CreateNotecardAsset(); + a1.Local = true; + a1.Temporary = true; + + lasc.Store(a1); + + Assert.That(lasc.Get(a1.ID), Is.Null); + Assert.That(lasc.GetData(a1.ID), Is.Null); + Assert.That(lasc.GetMetadata(a1.ID), Is.Null); + + // TODO: Add cache and check that this does receive a copy of the asset + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs new file mode 100644 index 00000000000..620bbf89053 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Grid/Tests/GridConnectorsTests.cs @@ -0,0 +1,192 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid.Tests +{ + [TestFixture] + public class GridConnectorsTests : OpenSimTestCase + { + RegionGridServicesConnector m_LocalConnector; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("GridService"); + config.Configs["Modules"].Set("GridServices", "RegionGridServicesConnector"); + config.Configs["GridService"].Set("LocalServiceModule", "OpenSim.Services.GridService.dll:GridService"); + config.Configs["GridService"].Set("StorageProvider", "OpenSim.Data.Null.dll"); + config.Configs["GridService"].Set("Region_Test_Region_1", "DefaultRegion"); + config.Configs["GridService"].Set("Region_Test_Region_2", "FallbackRegion"); + config.Configs["GridService"].Set("Region_Test_Region_3", "FallbackRegion"); + config.Configs["GridService"].Set("Region_Other_Region_4", "FallbackRegion"); + + m_LocalConnector = new RegionGridServicesConnector(config); + } + + /// + /// Test region registration. + /// + [Test] + public void TestRegisterRegion() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Create 4 regions + GridRegion r1 = new GridRegion(); + r1.RegionName = "Test Region 1"; + r1.RegionID = new UUID(1); + r1.RegionLocX = 1000 * (int)Constants.RegionSize; + r1.RegionLocY = 1000 * (int)Constants.RegionSize; + r1.ExternalHostName = "127.0.0.1"; + r1.HttpPort = 9001; + r1.InternalEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("0.0.0.0"), 0); + Scene s = new Scene(new RegionInfo()); + s.RegionInfo.RegionID = r1.RegionID; + m_LocalConnector.AddRegion(s); + + GridRegion r2 = new GridRegion(); + r2.RegionName = "Test Region 2"; + r2.RegionID = new UUID(2); + r2.RegionLocX = 1001 * (int)Constants.RegionSize; + r2.RegionLocY = 1000 * (int)Constants.RegionSize; + r2.ExternalHostName = "127.0.0.1"; + r2.HttpPort = 9002; + r2.InternalEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("0.0.0.0"), 0); + s = new Scene(new RegionInfo()); + s.RegionInfo.RegionID = r2.RegionID; + m_LocalConnector.AddRegion(s); + + GridRegion r3 = new GridRegion(); + r3.RegionName = "Test Region 3"; + r3.RegionID = new UUID(3); + r3.RegionLocX = 1005 * (int)Constants.RegionSize; + r3.RegionLocY = 1000 * (int)Constants.RegionSize; + r3.ExternalHostName = "127.0.0.1"; + r3.HttpPort = 9003; + r3.InternalEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("0.0.0.0"), 0); + s = new Scene(new RegionInfo()); + s.RegionInfo.RegionID = r3.RegionID; + m_LocalConnector.AddRegion(s); + + GridRegion r4 = new GridRegion(); + r4.RegionName = "Other Region 4"; + r4.RegionID = new UUID(4); + r4.RegionLocX = 1004 * (int)Constants.RegionSize; + r4.RegionLocY = 1002 * (int)Constants.RegionSize; + r4.ExternalHostName = "127.0.0.1"; + r4.HttpPort = 9004; + r4.InternalEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("0.0.0.0"), 0); + s = new Scene(new RegionInfo()); + s.RegionInfo.RegionID = r4.RegionID; + m_LocalConnector.AddRegion(s); + + m_LocalConnector.RegisterRegion(UUID.Zero, r1); + + GridRegion result = m_LocalConnector.GetRegionByName(UUID.Zero, "Test"); + Assert.IsNull(result, "Retrieved GetRegionByName \"Test\" is not null"); + + result = m_LocalConnector.GetRegionByName(UUID.Zero, "Test Region 1"); + Assert.IsNotNull(result, "Retrieved GetRegionByName is null"); + Assert.That(result.RegionName, Is.EqualTo("Test Region 1"), "Retrieved region's name does not match"); + + m_LocalConnector.RegisterRegion(UUID.Zero, r2); + m_LocalConnector.RegisterRegion(UUID.Zero, r3); + m_LocalConnector.RegisterRegion(UUID.Zero, r4); + + result = m_LocalConnector.GetRegionByUUID(UUID.Zero, new UUID(1)); + Assert.IsNotNull(result, "Retrieved GetRegionByUUID is null"); + Assert.That(result.RegionID, Is.EqualTo(new UUID(1)), "Retrieved region's UUID does not match"); + + result = m_LocalConnector.GetRegionByPosition(UUID.Zero, (int)Util.RegionToWorldLoc(1000), (int)Util.RegionToWorldLoc(1000)); + Assert.IsNotNull(result, "Retrieved GetRegionByPosition is null"); + Assert.That(result.RegionLocX, Is.EqualTo(1000 * (int)Constants.RegionSize), "Retrieved region's position does not match"); + + List results = m_LocalConnector.GetNeighbours(UUID.Zero, new UUID(1)); + Assert.IsNotNull(results, "Retrieved neighbours list is null"); + Assert.That(results.Count, Is.EqualTo(1), "Retrieved neighbour collection is greater than expected"); + Assert.That(results[0].RegionID, Is.EqualTo(new UUID(2)), "Retrieved region's UUID does not match"); + + results = m_LocalConnector.GetRegionsByName(UUID.Zero, "Test", 10); + Assert.IsNotNull(results, "Retrieved GetRegionsByName collection is null"); + Assert.That(results.Count, Is.EqualTo(3), "Retrieved neighbour collection is less than expected"); + + results = m_LocalConnector.GetRegionRange(UUID.Zero, 900 * (int)Constants.RegionSize, 1002 * (int)Constants.RegionSize, + 900 * (int)Constants.RegionSize, 1100 * (int)Constants.RegionSize); + Assert.IsNotNull(results, "Retrieved GetRegionRange collection is null"); + Assert.That(results.Count, Is.EqualTo(2), "Retrieved neighbour collection is not the number expected"); + + results = m_LocalConnector.GetDefaultRegions(UUID.Zero); + Assert.IsNotNull(results, "Retrieved GetDefaultRegions collection is null"); + Assert.That(results.Count, Is.EqualTo(1), "Retrieved default regions collection has not the expected size"); + Assert.That(results[0].RegionID, Is.EqualTo(new UUID(1)), "Retrieved default region's UUID does not match"); + + results = m_LocalConnector.GetFallbackRegions(UUID.Zero, r1.RegionLocX, r1.RegionLocY); + Assert.IsNotNull(results, "Retrieved GetFallbackRegions collection for region 1 is null"); + Assert.That(results.Count, Is.EqualTo(3), "Retrieved fallback regions collection for region 1 has not the expected size"); + Assert.That(results[0].RegionID, Is.EqualTo(new UUID(2)), "Retrieved fallback regions for default region are not in the expected order 2-4-3"); + Assert.That(results[1].RegionID, Is.EqualTo(new UUID(4)), "Retrieved fallback regions for default region are not in the expected order 2-4-3"); + Assert.That(results[2].RegionID, Is.EqualTo(new UUID(3)), "Retrieved fallback regions for default region are not in the expected order 2-4-3"); + + results = m_LocalConnector.GetFallbackRegions(UUID.Zero, r2.RegionLocX, r2.RegionLocY); + Assert.IsNotNull(results, "Retrieved GetFallbackRegions collection for region 2 is null"); + Assert.That(results.Count, Is.EqualTo(3), "Retrieved fallback regions collection for region 2 has not the expected size"); + Assert.That(results[0].RegionID, Is.EqualTo(new UUID(2)), "Retrieved fallback regions are not in the expected order 2-4-3"); + Assert.That(results[1].RegionID, Is.EqualTo(new UUID(4)), "Retrieved fallback regions are not in the expected order 2-4-3"); + Assert.That(results[2].RegionID, Is.EqualTo(new UUID(3)), "Retrieved fallback regions are not in the expected order 2-4-3"); + + results = m_LocalConnector.GetFallbackRegions(UUID.Zero, r3.RegionLocX, r3.RegionLocY); + Assert.IsNotNull(results, "Retrieved GetFallbackRegions collection for region 3 is null"); + Assert.That(results.Count, Is.EqualTo(3), "Retrieved fallback regions collection for region 3 has not the expected size"); + Assert.That(results[0].RegionID, Is.EqualTo(new UUID(3)), "Retrieved fallback regions are not in the expected order 3-4-2"); + Assert.That(results[1].RegionID, Is.EqualTo(new UUID(4)), "Retrieved fallback regions are not in the expected order 3-4-2"); + Assert.That(results[2].RegionID, Is.EqualTo(new UUID(2)), "Retrieved fallback regions are not in the expected order 3-4-2"); + + results = m_LocalConnector.GetFallbackRegions(UUID.Zero, r4.RegionLocX, r4.RegionLocY); + Assert.IsNotNull(results, "Retrieved GetFallbackRegions collection for region 4 is null"); + Assert.That(results.Count, Is.EqualTo(3), "Retrieved fallback regions collection for region 4 has not the expected size"); + Assert.That(results[0].RegionID, Is.EqualTo(new UUID(4)), "Retrieved fallback regions are not in the expected order 4-3-2"); + Assert.That(results[1].RegionID, Is.EqualTo(new UUID(3)), "Retrieved fallback regions are not in the expected order 4-3-2"); + Assert.That(results[2].RegionID, Is.EqualTo(new UUID(2)), "Retrieved fallback regions are not in the expected order 4-3-2"); + + results = m_LocalConnector.GetHyperlinks(UUID.Zero); + Assert.IsNotNull(results, "Retrieved GetHyperlinks list is null"); + Assert.That(results.Count, Is.EqualTo(0), "Retrieved linked regions collection is not the number expected"); + } + } +} diff --git a/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Presence/Tests/PresenceConnectorsTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Presence/Tests/PresenceConnectorsTests.cs new file mode 100644 index 00000000000..77700ff44bb --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/ServiceConnectorsOut/Presence/Tests/PresenceConnectorsTests.cs @@ -0,0 +1,106 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using PresenceInfo = OpenSim.Services.Interfaces.PresenceInfo; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.ServiceConnectorsOut.Presence.Tests +{ + [TestFixture] + public class PresenceConnectorsTests : OpenSimTestCase + { + LocalPresenceServicesConnector m_LocalConnector; + + public override void SetUp() + { + base.SetUp(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("PresenceService"); + config.Configs["Modules"].Set("PresenceServices", "LocalPresenceServicesConnector"); + config.Configs["PresenceService"].Set("LocalServiceModule", "OpenSim.Services.PresenceService.dll:PresenceService"); + config.Configs["PresenceService"].Set("StorageProvider", "OpenSim.Data.Null.dll"); + + m_LocalConnector = new LocalPresenceServicesConnector(); + m_LocalConnector.Initialise(config); + + // Let's stick in a test presence + m_LocalConnector.m_PresenceService.LoginAgent(UUID.Zero.ToString(), UUID.Zero, UUID.Zero); + } + + /// + /// Test OpenSim Presence. + /// + [Test] + public void TestPresenceV0_1() + { + SetUp(); + + // Let's stick in a test presence + /* + PresenceData p = new PresenceData(); + p.SessionID = UUID.Zero; + p.UserID = UUID.Zero.ToString(); + p.Data = new Dictionary(); + p.Data["Online"] = true.ToString(); + m_presenceData.Add(UUID.Zero, p); + */ + + string user1 = UUID.Zero.ToString(); + UUID session1 = UUID.Zero; + + // this is not implemented by this connector + //m_LocalConnector.LoginAgent(user1, session1, UUID.Zero); + PresenceInfo result = m_LocalConnector.GetAgent(session1); + Assert.IsNotNull(result, "Retrieved GetAgent is null"); + Assert.That(result.UserID, Is.EqualTo(user1), "Retrieved userID does not match"); + + UUID region1 = UUID.Random(); + bool r = m_LocalConnector.ReportAgent(session1, region1); + Assert.IsTrue(r, "First ReportAgent returned false"); + result = m_LocalConnector.GetAgent(session1); + Assert.That(result.RegionID, Is.EqualTo(region1), "Agent is not in the right region (region1)"); + + UUID region2 = UUID.Random(); + r = m_LocalConnector.ReportAgent(session1, region2); + Assert.IsTrue(r, "Second ReportAgent returned false"); + result = m_LocalConnector.GetAgent(session1); + Assert.That(result.RegionID, Is.EqualTo(region2), "Agent is not in the right region (region2)"); + + r = m_LocalConnector.LogoutAgent(session1); + Assert.IsTrue(r, "LogoutAgent returned false"); + result = m_LocalConnector.GetAgent(session1); + Assert.IsNull(result, "Agent session is still stored after logout"); + + r = m_LocalConnector.ReportAgent(session1, region1); + Assert.IsFalse(r, "ReportAgent of non-logged in user returned true"); + } + } +} diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/ArchiverTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/ArchiverTests.cs new file mode 100644 index 00000000000..425876df99d --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/ArchiverTests.cs @@ -0,0 +1,1048 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Reflection; +using OpenMetaverse; +using OpenMetaverse.Assets; + +using OpenSim.Framework; +using OpenSim.Framework.Serialization.External; +using OpenSim.Region.CoreModules.World.Land; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; +using OpenSim.Tests.Common; +using ArchiveConstants = OpenSim.Framework.Serialization.ArchiveConstants; +using TarArchiveReader = OpenSim.Framework.Serialization.TarArchiveReader; +using TarArchiveWriter = OpenSim.Framework.Serialization.TarArchiveWriter; +using RegionSettings = OpenSim.Framework.RegionSettings; + +namespace OpenSim.Region.CoreModules.World.Archiver.Tests +{ + [TestFixture] + public class ArchiverTests : OpenSimTestCase + { + private Guid m_lastRequestId; + private string m_lastErrorMessage; + + protected SceneHelpers m_sceneHelpers; + protected TestScene m_scene; + protected ArchiverModule m_archiverModule; + protected SerialiserModule m_serialiserModule; + + protected TaskInventoryItem m_soundItem; + + private AutoResetEvent m_oarEvent = new AutoResetEvent(false); + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_archiverModule = new ArchiverModule(); + m_serialiserModule = new SerialiserModule(); + TerrainModule terrainModule = new TerrainModule(); + + m_sceneHelpers = new SceneHelpers(); + m_scene = m_sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, m_archiverModule, m_serialiserModule, terrainModule); + } + + private void LoadCompleted(Guid requestId, List loadedScenes, string errorMessage) + { + lock (this) + { + m_lastRequestId = requestId; + m_lastErrorMessage = errorMessage; + Console.WriteLine("About to pulse ArchiverTests on LoadCompleted"); + m_oarEvent.Set(); + } + } + + private void SaveCompleted(Guid requestId, string errorMessage) + { + lock (this) + { + m_lastRequestId = requestId; + m_lastErrorMessage = errorMessage; + Console.WriteLine("About to pulse ArchiverTests on SaveCompleted"); + m_oarEvent.Set(); + } + } + + protected SceneObjectPart CreateSceneObjectPart1() + { + string partName = "My Little Pony"; + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000015"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); + Vector3 groupPosition = new Vector3(10, 20, 30); + Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); + rotationOffset.Normalize(); +// Vector3 offsetPosition = new Vector3(5, 10, 15); + + return new SceneObjectPart(ownerId, shape, groupPosition, rotationOffset, Vector3.Zero) { Name = partName }; + } + + protected SceneObjectPart CreateSceneObjectPart2() + { + string partName = "Action Man"; + UUID ownerId = UUID.Parse("00000000-0000-0000-0000-000000000016"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateCylinder(); + Vector3 groupPosition = new Vector3(90, 80, 70); + Quaternion rotationOffset = new Quaternion(60, 70, 80, 90); + rotationOffset.Normalize(); + Vector3 offsetPosition = new Vector3(20, 25, 30); + + return new SceneObjectPart(ownerId, shape, groupPosition, rotationOffset, offsetPosition) { Name = partName }; + } + + private void CreateTestObjects(Scene scene, out SceneObjectGroup sog1, out SceneObjectGroup sog2, out UUID ncAssetUuid) + { + SceneObjectPart part1 = CreateSceneObjectPart1(); + sog1 = new SceneObjectGroup(part1); + scene.AddNewSceneObject(sog1, false); + + AssetNotecard nc = new AssetNotecard(); + nc.BodyText = "Hello World!"; + nc.Encode(); + ncAssetUuid = UUID.Random(); + UUID ncItemUuid = UUID.Random(); + AssetBase ncAsset + = AssetHelpers.CreateAsset(ncAssetUuid, AssetType.Notecard, nc.AssetData, UUID.Zero); + m_scene.AssetService.Store(ncAsset); + + TaskInventoryItem ncItem + = new TaskInventoryItem { Name = "ncItem", AssetID = ncAssetUuid, ItemID = ncItemUuid }; + SceneObjectPart part2 = CreateSceneObjectPart2(); + sog2 = new SceneObjectGroup(part2); + part2.Inventory.AddInventoryItem(ncItem, true); + + scene.AddNewSceneObject(sog2, false); + } + + private static void CreateSoundAsset(TarArchiveWriter tar, Assembly assembly, string soundDataResourceName, out byte[] soundData, out UUID soundUuid) + { + using (Stream resource = assembly.GetManifestResourceStream(soundDataResourceName)) + { + using (BinaryReader br = new BinaryReader(resource)) + { + // FIXME: Use the inspector instead + soundData = br.ReadBytes(99999999); + soundUuid = UUID.Parse("00000000-0000-0000-0000-000000000001"); + string soundAssetFileName + = ArchiveConstants.ASSETS_PATH + soundUuid + + ArchiveConstants.ASSET_TYPE_TO_EXTENSION[(sbyte)AssetType.SoundWAV]; + tar.WriteFile(soundAssetFileName, soundData); + + /* + AssetBase soundAsset = AssetHelpers.CreateAsset(soundUuid, soundData); + scene.AssetService.Store(soundAsset); + asset1FileName = ArchiveConstants.ASSETS_PATH + soundUuid + ".wav"; + */ + } + } + } + + /// + /// Test saving an OpenSim Region Archive. + /// + [Test] + public void TestSaveOar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectGroup sog1; + SceneObjectGroup sog2; + UUID ncAssetUuid; + CreateTestObjects(m_scene, out sog1, out sog2, out ncAssetUuid); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_scene.EventManager.OnOarFileSaved += SaveCompleted; + + Guid requestId = new Guid("00000000-0000-0000-0000-808080808080"); + + m_oarEvent.Reset(); + m_archiverModule.ArchiveRegion(archiveWriteStream, requestId); + //AssetServerBase assetServer = (AssetServerBase)scene.CommsManager.AssetCache.AssetServer; + //while (assetServer.HasWaitingRequests()) + // assetServer.ProcessNextRequest(); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastRequestId, Is.EqualTo(requestId)); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + bool gotNcAssetFile = false; + + string expectedNcAssetFileName = string.Format("{0}_{1}", ncAssetUuid, "notecard.txt"); + + List foundPaths = new List(); + List expectedPaths = new List(); + expectedPaths.Add(ArchiveHelpers.CreateObjectPath(sog1)); + expectedPaths.Add(ArchiveHelpers.CreateObjectPath(sog2)); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + byte[] data = tar.ReadEntry(out filePath, out tarEntryType); + Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); + + Dictionary archiveOptions = new Dictionary(); + ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, Guid.Empty, archiveOptions); + arr.LoadControlFile(filePath, data, new DearchiveScenesInfo()); + + Assert.That(arr.ControlFileLoaded, Is.True); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + string fileName = filePath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); + + Assert.That(fileName, Is.EqualTo(expectedNcAssetFileName)); + gotNcAssetFile = true; + } + else if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) + { + foundPaths.Add(filePath); + } + } + + Assert.That(gotNcAssetFile, Is.True, "No notecard asset file in archive"); + Assert.That(foundPaths, Is.EquivalentTo(expectedPaths)); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test saving an OpenSim Region Archive with the no assets option + /// + [Test] + public void TestSaveOarNoAssets() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectPart part1 = CreateSceneObjectPart1(); + SceneObjectGroup sog1 = new SceneObjectGroup(part1); + m_scene.AddNewSceneObject(sog1, false); + + SceneObjectPart part2 = CreateSceneObjectPart2(); + + AssetNotecard nc = new AssetNotecard(); + nc.BodyText = "Hello World!"; + nc.Encode(); + UUID ncAssetUuid = new UUID("00000000-0000-0000-1000-000000000000"); + UUID ncItemUuid = new UUID("00000000-0000-0000-1100-000000000000"); + AssetBase ncAsset + = AssetHelpers.CreateAsset(ncAssetUuid, AssetType.Notecard, nc.AssetData, UUID.Zero); + m_scene.AssetService.Store(ncAsset); + SceneObjectGroup sog2 = new SceneObjectGroup(part2); + TaskInventoryItem ncItem + = new TaskInventoryItem { Name = "ncItem", AssetID = ncAssetUuid, ItemID = ncItemUuid }; + part2.Inventory.AddInventoryItem(ncItem, true); + + m_scene.AddNewSceneObject(sog2, false); + + MemoryStream archiveWriteStream = new MemoryStream(); + + Guid requestId = new Guid("00000000-0000-0000-0000-808080808080"); + + Dictionary options = new Dictionary(); + options.Add("noassets", true); + + m_archiverModule.ArchiveRegion(archiveWriteStream, requestId, options); + + // Don't wait for completion - with --noassets save oar happens synchronously +// Monitor.Wait(this, 60000); + + Assert.That(m_lastRequestId, Is.EqualTo(requestId)); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + List foundPaths = new List(); + List expectedPaths = new List(); + expectedPaths.Add(ArchiveHelpers.CreateObjectPath(sog1)); + expectedPaths.Add(ArchiveHelpers.CreateObjectPath(sog2)); + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + byte[] data = tar.ReadEntry(out filePath, out tarEntryType); + Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); + + Dictionary archiveOptions = new Dictionary(); + ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, Guid.Empty, archiveOptions); + arr.LoadControlFile(filePath, data, new DearchiveScenesInfo()); + + Assert.That(arr.ControlFileLoaded, Is.True); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + Assert.Fail("Asset was found in saved oar of TestSaveOarNoAssets()"); + } + else if (filePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) + { + foundPaths.Add(filePath); + } + } + + Assert.That(foundPaths, Is.EquivalentTo(expectedPaths)); + + // TODO: Test presence of more files and contents of files. + } + + /// + /// Test loading an OpenSim Region Archive. + /// + [Test] + public void TestLoadOar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + MemoryStream archiveWriteStream = new MemoryStream(); + TarArchiveWriter tar = new TarArchiveWriter(archiveWriteStream); + + // Put in a random blank directory to check that this doesn't upset the load process + tar.WriteDir("ignoreme"); + + // Also check that direct entries which will also have a file entry containing that directory doesn't + // upset load + tar.WriteDir(ArchiveConstants.TERRAINS_PATH); + + tar.WriteFile( + ArchiveConstants.CONTROL_FILE_PATH, + new ArchiveWriteRequest(m_scene, (Stream)null, Guid.Empty).CreateControlFile(new ArchiveScenesGroup())); + SceneObjectPart part1 = CreateSceneObjectPart1(); + + part1.SitTargetOrientation = new Quaternion(0.2f, 0.3f, 0.4f, 0.5f); + part1.SitTargetPosition = new Vector3(1, 2, 3); + + SceneObjectGroup object1 = new SceneObjectGroup(part1); + + // Let's put some inventory items into our object + string soundItemName = "sound-item1"; + UUID soundItemUuid = UUID.Parse("00000000-0000-0000-0000-000000000002"); + Type type = GetType(); + Assembly assembly = type.Assembly; + string soundDataResourceName = null; + string[] names = assembly.GetManifestResourceNames(); + foreach (string name in names) + { + if (name.EndsWith(".Resources.test-sound.wav")) + soundDataResourceName = name; + } + Assert.That(soundDataResourceName, Is.Not.Null); + + byte[] soundData; + UUID soundUuid; + CreateSoundAsset(tar, assembly, soundDataResourceName, out soundData, out soundUuid); + + TaskInventoryItem item1 + = new TaskInventoryItem { AssetID = soundUuid, ItemID = soundItemUuid, Name = soundItemName }; + part1.Inventory.AddInventoryItem(item1, true); + m_scene.AddNewSceneObject(object1, false); + + string object1FileName = string.Format( + "{0}_{1:000}-{2:000}-{3:000}__{4}.xml", + part1.Name, + Math.Round(part1.GroupPosition.X), Math.Round(part1.GroupPosition.Y), Math.Round(part1.GroupPosition.Z), + part1.UUID); + tar.WriteFile(ArchiveConstants.OBJECTS_PATH + object1FileName, SceneObjectSerializer.ToXml2Format(object1)); + + tar.Close(); + + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + m_scene.EventManager.OnOarFileLoaded += LoadCompleted; + m_oarEvent.Reset(); + m_archiverModule.DearchiveRegion(archiveReadStream); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastErrorMessage, Is.Null); + + TestLoadedRegion(part1, soundItemName, soundData); + } + + /// + /// Test loading an OpenSim Region Archive where the scene object parts are not ordered by link number (e.g. + /// 2 can come after 3). + /// + [Test] + public void TestLoadOarUnorderedParts() + { + TestHelpers.InMethod(); + + UUID ownerId = TestHelpers.ParseTail(0xaaaa); + + MemoryStream archiveWriteStream = new MemoryStream(); + TarArchiveWriter tar = new TarArchiveWriter(archiveWriteStream); + + tar.WriteFile( + ArchiveConstants.CONTROL_FILE_PATH, + new ArchiveWriteRequest(m_scene, (Stream)null, Guid.Empty).CreateControlFile(new ArchiveScenesGroup())); + + SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(1, ownerId, "obj1-", 0x11); + SceneObjectPart sop2 + = SceneHelpers.CreateSceneObjectPart("obj1-Part2", TestHelpers.ParseTail(0x12), ownerId); + SceneObjectPart sop3 + = SceneHelpers.CreateSceneObjectPart("obj1-Part3", TestHelpers.ParseTail(0x13), ownerId); + + // Add the parts so they will be written out in reverse order to the oar + sog1.AddPart(sop3); + sop3.LinkNum = 3; + sog1.AddPart(sop2); + sop2.LinkNum = 2; + + tar.WriteFile( + ArchiveConstants.CreateOarObjectPath(sog1.Name, sog1.UUID, sog1.AbsolutePosition), + SceneObjectSerializer.ToXml2Format(sog1)); + + tar.Close(); + + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + m_scene.EventManager.OnOarFileLoaded += LoadCompleted; + m_oarEvent.Reset(); + m_archiverModule.DearchiveRegion(archiveReadStream); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastErrorMessage, Is.Null); + + SceneObjectPart part2 = m_scene.GetSceneObjectPart("obj1-Part2"); + Assert.That(part2.LinkNum, Is.EqualTo(2)); + + SceneObjectPart part3 = m_scene.GetSceneObjectPart("obj1-Part3"); + Assert.That(part3.LinkNum, Is.EqualTo(3)); + } + + /// + /// Test loading an OpenSim Region Archive saved with the --publish option. + /// + [Test] + public void TestLoadPublishedOar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectPart part1 = CreateSceneObjectPart1(); + SceneObjectGroup sog1 = new SceneObjectGroup(part1); + m_scene.AddNewSceneObject(sog1, false); + + SceneObjectPart part2 = CreateSceneObjectPart2(); + + AssetNotecard nc = new AssetNotecard(); + nc.BodyText = "Hello World!"; + nc.Encode(); + UUID ncAssetUuid = new UUID("00000000-0000-0000-1000-000000000000"); + UUID ncItemUuid = new UUID("00000000-0000-0000-1100-000000000000"); + AssetBase ncAsset + = AssetHelpers.CreateAsset(ncAssetUuid, AssetType.Notecard, nc.AssetData, UUID.Zero); + m_scene.AssetService.Store(ncAsset); + SceneObjectGroup sog2 = new SceneObjectGroup(part2); + TaskInventoryItem ncItem + = new TaskInventoryItem { Name = "ncItem", AssetID = ncAssetUuid, ItemID = ncItemUuid }; + part2.Inventory.AddInventoryItem(ncItem, true); + + m_scene.AddNewSceneObject(sog2, false); + + MemoryStream archiveWriteStream = new MemoryStream(); + m_scene.EventManager.OnOarFileSaved += SaveCompleted; + + Guid requestId = new Guid("00000000-0000-0000-0000-808080808080"); + + m_oarEvent.Reset(); + m_archiverModule.ArchiveRegion( + archiveWriteStream, requestId, new Dictionary() { { "wipe-owners", Boolean.TrueString } }); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastRequestId, Is.EqualTo(requestId)); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + + { + UUID estateOwner = TestHelpers.ParseTail(0x4747); + UUID objectOwner = TestHelpers.ParseTail(0x15); + + // Reload to new scene + ArchiverModule archiverModule = new ArchiverModule(); + SerialiserModule serialiserModule = new SerialiserModule(); + TerrainModule terrainModule = new TerrainModule(); + + SceneHelpers m_sceneHelpers2 = new SceneHelpers(); + TestScene scene2 = m_sceneHelpers2.SetupScene(); + SceneHelpers.SetupSceneModules(scene2, archiverModule, serialiserModule, terrainModule); + + // Make sure there's a valid owner for the owner we saved (this should have been wiped if the code is + // behaving correctly + UserAccountHelpers.CreateUserWithInventory(scene2, objectOwner); + + scene2.RegionInfo.EstateSettings.EstateOwner = estateOwner; + + scene2.EventManager.OnOarFileLoaded += LoadCompleted; + m_oarEvent.Reset(); + archiverModule.DearchiveRegion(archiveReadStream); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastErrorMessage, Is.Null); + + SceneObjectGroup loadedSog = scene2.GetSceneObjectGroup(part1.Name); + Assert.That(loadedSog.OwnerID, Is.EqualTo(estateOwner)); + Assert.That(loadedSog.LastOwnerID, Is.EqualTo(estateOwner)); + } + } + + /// + /// Test OAR loading where the land parcel is group deeded. + /// + /// + /// In this situation, the owner ID is set to the group ID. + /// + [Test] + public void TestLoadOarDeededLand() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID landID = TestHelpers.ParseTail(0x10); + + MockGroupsServicesConnector groupsService = new MockGroupsServicesConnector(); + + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Groups"); + config.Set("Enabled", true); + config.Set("Module", "GroupsModule"); + config.Set("DebugEnabled", true); + SceneHelpers.SetupSceneModules( + m_scene, configSource, new object[] { new GroupsModule(), groupsService, new LandManagementModule() }); + + // Create group in scene for loading + // FIXME: For now we'll put up with the issue that we'll get a group ID that varies across tests. + UUID groupID + = groupsService.CreateGroup(UUID.Zero, "group1", "", true, UUID.Zero, 3, true, true, true, UUID.Zero); + + // Construct OAR + MemoryStream oarStream = new MemoryStream(); + TarArchiveWriter tar = new TarArchiveWriter(oarStream); + + tar.WriteDir(ArchiveConstants.LANDDATA_PATH); + tar.WriteFile( + ArchiveConstants.CONTROL_FILE_PATH, + new ArchiveWriteRequest(m_scene, (Stream)null, Guid.Empty).CreateControlFile(new ArchiveScenesGroup())); + + LandObject lo = new LandObject(groupID, true, m_scene); + lo.SetLandBitmap(lo.BasicFullRegionLandBitmap()); + LandData ld = lo.LandData; + ld.GlobalID = landID; + + string ldPath = ArchiveConstants.CreateOarLandDataPath(ld); + Dictionary options = new Dictionary(); + tar.WriteFile(ldPath, LandDataSerializer.Serialize(ld, options)); + tar.Close(); + + oarStream = new MemoryStream(oarStream.ToArray()); + + // Load OAR + m_scene.EventManager.OnOarFileLoaded += LoadCompleted; + m_oarEvent.Reset(); + m_archiverModule.DearchiveRegion(oarStream); + + m_oarEvent.WaitOne(60000); + + ILandObject rLo = m_scene.LandChannel.GetLandObject(16, 16); + LandData rLd = rLo.LandData; + + Assert.That(rLd.GlobalID, Is.EqualTo(landID)); + Assert.That(rLd.OwnerID, Is.EqualTo(groupID)); + Assert.That(rLd.GroupID, Is.EqualTo(groupID)); + Assert.That(rLd.IsGroupOwned, Is.EqualTo(true)); + } + + /// + /// Test loading the region settings of an OpenSim Region Archive. + /// + [Test] + public void TestLoadOarRegionSettings() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + MemoryStream archiveWriteStream = new MemoryStream(); + TarArchiveWriter tar = new TarArchiveWriter(archiveWriteStream); + + tar.WriteDir(ArchiveConstants.TERRAINS_PATH); + tar.WriteFile( + ArchiveConstants.CONTROL_FILE_PATH, + new ArchiveWriteRequest(m_scene, (Stream)null, Guid.Empty).CreateControlFile(new ArchiveScenesGroup())); + + RegionSettings rs = new RegionSettings(); + rs.AgentLimit = 17; + rs.AllowDamage = true; + rs.AllowLandJoinDivide = true; + rs.AllowLandResell = true; + rs.BlockFly = true; + rs.BlockShowInSearch = true; + rs.BlockTerraform = true; + rs.DisableCollisions = true; + rs.DisablePhysics = true; + rs.DisableScripts = true; + rs.Elevation1NW = 15.9; + rs.Elevation1NE = 45.3; + rs.Elevation1SE = 49; + rs.Elevation1SW = 1.9; + rs.Elevation2NW = 4.5; + rs.Elevation2NE = 19.2; + rs.Elevation2SE = 9.2; + rs.Elevation2SW = 2.1; + rs.FixedSun = true; + rs.SunPosition = 12.0; + rs.ObjectBonus = 1.4; + rs.RestrictPushing = true; + rs.TerrainLowerLimit = -17.9; + rs.TerrainRaiseLimit = 17.9; + rs.TerrainTexture1 = UUID.Parse("00000000-0000-0000-0000-000000000020"); + rs.TerrainTexture2 = UUID.Parse("00000000-0000-0000-0000-000000000040"); + rs.TerrainTexture3 = UUID.Parse("00000000-0000-0000-0000-000000000060"); + rs.TerrainTexture4 = UUID.Parse("00000000-0000-0000-0000-000000000080"); + rs.UseEstateSun = true; + rs.WaterHeight = 23; + rs.TelehubObject = UUID.Parse("00000000-0000-0000-0000-111111111111"); + rs.AddSpawnPoint(SpawnPoint.Parse("1,-2,0.33")); + + tar.WriteFile(ArchiveConstants.SETTINGS_PATH + "region1.xml", RegionSettingsSerializer.Serialize(rs, null, new EstateSettings())); + + tar.Close(); + + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + m_scene.EventManager.OnOarFileLoaded += LoadCompleted; + m_oarEvent.Reset(); + m_archiverModule.DearchiveRegion(archiveReadStream); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastErrorMessage, Is.Null); + RegionSettings loadedRs = m_scene.RegionInfo.RegionSettings; + + Assert.That(loadedRs.AgentLimit, Is.EqualTo(17)); + Assert.That(loadedRs.AllowDamage, Is.True); + Assert.That(loadedRs.AllowLandJoinDivide, Is.True); + Assert.That(loadedRs.AllowLandResell, Is.True); + Assert.That(loadedRs.BlockFly, Is.True); + Assert.That(loadedRs.BlockShowInSearch, Is.True); + Assert.That(loadedRs.BlockTerraform, Is.True); + Assert.That(loadedRs.DisableCollisions, Is.True); + Assert.That(loadedRs.DisablePhysics, Is.True); + Assert.That(loadedRs.DisableScripts, Is.True); + Assert.That(loadedRs.Elevation1NW, Is.EqualTo(15.9)); + Assert.That(loadedRs.Elevation1NE, Is.EqualTo(45.3)); + Assert.That(loadedRs.Elevation1SE, Is.EqualTo(49)); + Assert.That(loadedRs.Elevation1SW, Is.EqualTo(1.9)); + Assert.That(loadedRs.Elevation2NW, Is.EqualTo(4.5)); + Assert.That(loadedRs.Elevation2NE, Is.EqualTo(19.2)); + Assert.That(loadedRs.Elevation2SE, Is.EqualTo(9.2)); + Assert.That(loadedRs.Elevation2SW, Is.EqualTo(2.1)); + Assert.That(loadedRs.ObjectBonus, Is.EqualTo(1.4)); + Assert.That(loadedRs.RestrictPushing, Is.True); + Assert.That(loadedRs.TerrainLowerLimit, Is.EqualTo(-17.9)); + Assert.That(loadedRs.TerrainRaiseLimit, Is.EqualTo(17.9)); + Assert.That(loadedRs.TerrainTexture1, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000020"))); + Assert.That(loadedRs.TerrainTexture2, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000040"))); + Assert.That(loadedRs.TerrainTexture3, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000060"))); + Assert.That(loadedRs.TerrainTexture4, Is.EqualTo(UUID.Parse("00000000-0000-0000-0000-000000000080"))); + Assert.That(loadedRs.WaterHeight, Is.EqualTo(23)); + Assert.AreEqual(UUID.Zero, loadedRs.TelehubObject); // because no object was found with the original UUID + Assert.AreEqual(0, loadedRs.SpawnPoints().Count); + } + + /// + /// Test merging an OpenSim Region Archive into an existing scene + /// + //[Test] + public void TestMergeOar() + { + TestHelpers.InMethod(); + //XmlConfigurator.Configure(); + + MemoryStream archiveWriteStream = new MemoryStream(); + +// string part2Name = "objectMerge"; +// PrimitiveBaseShape part2Shape = PrimitiveBaseShape.CreateCylinder(); +// Vector3 part2GroupPosition = new Vector3(90, 80, 70); +// Quaternion part2RotationOffset = new Quaternion(60, 70, 80, 90); +// Vector3 part2OffsetPosition = new Vector3(20, 25, 30); + + SceneObjectPart part2 = CreateSceneObjectPart2(); + + // Create an oar file that we can use for the merge + { + ArchiverModule archiverModule = new ArchiverModule(); + SerialiserModule serialiserModule = new SerialiserModule(); + TerrainModule terrainModule = new TerrainModule(); + + Scene scene = m_sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(scene, archiverModule, serialiserModule, terrainModule); + + m_scene.AddNewSceneObject(new SceneObjectGroup(part2), false); + + // Write out this scene + + scene.EventManager.OnOarFileSaved += SaveCompleted; + m_oarEvent.Reset(); + m_archiverModule.ArchiveRegion(archiveWriteStream); + + m_oarEvent.WaitOne(60000); + } + + { + SceneObjectPart part1 = CreateSceneObjectPart1(); + m_scene.AddNewSceneObject(new SceneObjectGroup(part1), false); + + // Merge in the archive we created earlier + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + + m_scene.EventManager.OnOarFileLoaded += LoadCompleted; + Dictionary archiveOptions = new Dictionary(); + archiveOptions.Add("merge", null); + m_oarEvent.Reset(); + m_archiverModule.DearchiveRegion(archiveReadStream, Guid.Empty, archiveOptions); + + m_oarEvent.WaitOne(60000); + + SceneObjectPart object1Existing = m_scene.GetSceneObjectPart(part1.Name); + Assert.That(object1Existing, Is.Not.Null, "object1 was not present after merge"); + Assert.That(object1Existing.Name, Is.EqualTo(part1.Name), "object1 names not identical after merge"); + Assert.That(object1Existing.GroupPosition, Is.EqualTo(part1.GroupPosition), "object1 group position not equal after merge"); + + SceneObjectPart object2PartMerged = m_scene.GetSceneObjectPart(part2.Name); + Assert.That(object2PartMerged, Is.Not.Null, "object2 was not present after merge"); + Assert.That(object2PartMerged.Name, Is.EqualTo(part2.Name), "object2 names not identical after merge"); + Assert.That(object2PartMerged.GroupPosition, Is.EqualTo(part2.GroupPosition), "object2 group position not equal after merge"); + } + } + + /// + /// Test saving a multi-region OAR. + /// + [Test] + public void TestSaveMultiRegionOar() + { + TestHelpers.InMethod(); + + // Create test regions + + int WIDTH = 2; + int HEIGHT = 2; + + List scenes = new List(); + + // Maps (Directory in OAR file -> scene) + Dictionary regionPaths = new Dictionary(); + + // Maps (Scene -> expected object paths) + Dictionary> expectedPaths = new Dictionary>(); + + // List of expected assets + List expectedAssets = new List(); + + for (uint y = 0; y < HEIGHT; y++) + { + for (uint x = 0; x < WIDTH; x++) + { + Scene scene; + if (x == 0 && y == 0) + { + scene = m_scene; // this scene was already created in SetUp() + } + else + { + scene = m_sceneHelpers.SetupScene(string.Format("Unit test region {0}", (y * WIDTH) + x + 1), UUID.Random(), 1000 + x, 1000 + y); + SceneHelpers.SetupSceneModules(scene, new ArchiverModule(), m_serialiserModule, new TerrainModule()); + } + scenes.Add(scene); + + string dir = String.Format("{0}_{1}_{2}", x + 1, y + 1, scene.RegionInfo.RegionName.Replace(" ", "_")); + regionPaths[dir] = scene; + + SceneObjectGroup sog1; + SceneObjectGroup sog2; + UUID ncAssetUuid; + + CreateTestObjects(scene, out sog1, out sog2, out ncAssetUuid); + + expectedPaths[scene.RegionInfo.RegionID] = new List(); + expectedPaths[scene.RegionInfo.RegionID].Add(ArchiveHelpers.CreateObjectPath(sog1)); + expectedPaths[scene.RegionInfo.RegionID].Add(ArchiveHelpers.CreateObjectPath(sog2)); + + expectedAssets.Add(ncAssetUuid); + } + } + + // Save OAR + MemoryStream archiveWriteStream = new MemoryStream(); + + Guid requestId = new Guid("00000000-0000-0000-0000-808080808080"); + + Dictionary options = new Dictionary(); + options.Add("all", true); + + m_scene.EventManager.OnOarFileSaved += SaveCompleted; + m_oarEvent.Reset(); + m_archiverModule.ArchiveRegion(archiveWriteStream, requestId, options); + + m_oarEvent.WaitOne(60000); + + + // Check that the OAR contains the expected data + Assert.That(m_lastRequestId, Is.EqualTo(requestId)); + + byte[] archive = archiveWriteStream.ToArray(); + MemoryStream archiveReadStream = new MemoryStream(archive); + TarArchiveReader tar = new TarArchiveReader(archiveReadStream); + + Dictionary> foundPaths = new Dictionary>(); + List foundAssets = new List(); + + foreach (Scene scene in scenes) + { + foundPaths[scene.RegionInfo.RegionID] = new List(); + } + + string filePath; + TarArchiveReader.TarEntryType tarEntryType; + + byte[] data = tar.ReadEntry(out filePath, out tarEntryType); + Assert.That(filePath, Is.EqualTo(ArchiveConstants.CONTROL_FILE_PATH)); + + Dictionary archiveOptions = new Dictionary(); + ArchiveReadRequest arr = new ArchiveReadRequest(m_scene, (Stream)null, Guid.Empty, archiveOptions); + arr.LoadControlFile(filePath, data, new DearchiveScenesInfo()); + + Assert.That(arr.ControlFileLoaded, Is.True); + + while (tar.ReadEntry(out filePath, out tarEntryType) != null) + { + if (filePath.StartsWith(ArchiveConstants.ASSETS_PATH)) + { + // Assets are shared, so this file doesn't belong to any specific region. + string fileName = filePath.Remove(0, ArchiveConstants.ASSETS_PATH.Length); + if (fileName.EndsWith("_notecard.txt")) + foundAssets.Add(UUID.Parse(fileName.Substring(0, fileName.Length - "_notecard.txt".Length))); + } + else + { + // This file belongs to one of the regions. Find out which one. + Assert.IsTrue(filePath.StartsWith(ArchiveConstants.REGIONS_PATH)); + string[] parts = filePath.Split(new Char[] { '/' }, 3); + Assert.AreEqual(3, parts.Length); + string regionDirectory = parts[1]; + string relativePath = parts[2]; + Scene scene = regionPaths[regionDirectory]; + + if (relativePath.StartsWith(ArchiveConstants.OBJECTS_PATH)) + { + foundPaths[scene.RegionInfo.RegionID].Add(relativePath); + } + } + } + + Assert.AreEqual(scenes.Count, foundPaths.Count); + foreach (Scene scene in scenes) + { + Assert.That(foundPaths[scene.RegionInfo.RegionID], Is.EquivalentTo(expectedPaths[scene.RegionInfo.RegionID])); + } + + Assert.That(foundAssets, Is.EquivalentTo(expectedAssets)); + } + + /// + /// Test loading a multi-region OAR. + /// + [Test] + public void TestLoadMultiRegionOar() + { + TestHelpers.InMethod(); + + // Create an ArchiveScenesGroup with the regions in the OAR. This is needed to generate the control file. + + int WIDTH = 2; + int HEIGHT = 2; + + for (uint y = 0; y < HEIGHT; y++) + { + for (uint x = 0; x < WIDTH; x++) + { + Scene scene; + if (x == 0 && y == 0) + { + scene = m_scene; // this scene was already created in SetUp() + } + else + { + scene = m_sceneHelpers.SetupScene(string.Format("Unit test region {0}", (y * WIDTH) + x + 1), UUID.Random(), 1000 + x, 1000 + y); + SceneHelpers.SetupSceneModules(scene, new ArchiverModule(), m_serialiserModule, new TerrainModule()); + } + } + } + + ArchiveScenesGroup scenesGroup = new ArchiveScenesGroup(); + m_sceneHelpers.SceneManager.ForEachScene(delegate(Scene scene) + { + scenesGroup.AddScene(scene); + }); + scenesGroup.CalcSceneLocations(); + + // Generate the OAR file + + MemoryStream archiveWriteStream = new MemoryStream(); + TarArchiveWriter tar = new TarArchiveWriter(archiveWriteStream); + + ArchiveWriteRequest writeRequest = new ArchiveWriteRequest(m_scene, (Stream)null, Guid.Empty); + writeRequest.MultiRegionFormat = true; + tar.WriteFile( + ArchiveConstants.CONTROL_FILE_PATH, writeRequest.CreateControlFile(scenesGroup)); + + SceneObjectPart part1 = CreateSceneObjectPart1(); + part1.SitTargetOrientation = new Quaternion(0.2f, 0.3f, 0.4f, 0.5f); + part1.SitTargetPosition = new Vector3(1, 2, 3); + + SceneObjectGroup object1 = new SceneObjectGroup(part1); + + // Let's put some inventory items into our object + string soundItemName = "sound-item1"; + UUID soundItemUuid = UUID.Parse("00000000-0000-0000-0000-000000000002"); + Type type = GetType(); + Assembly assembly = type.Assembly; + string soundDataResourceName = null; + string[] names = assembly.GetManifestResourceNames(); + foreach (string name in names) + { + if (name.EndsWith(".Resources.test-sound.wav")) + soundDataResourceName = name; + } + Assert.That(soundDataResourceName, Is.Not.Null); + + byte[] soundData; + UUID soundUuid; + CreateSoundAsset(tar, assembly, soundDataResourceName, out soundData, out soundUuid); + + TaskInventoryItem item1 + = new TaskInventoryItem { AssetID = soundUuid, ItemID = soundItemUuid, Name = soundItemName }; + part1.Inventory.AddInventoryItem(item1, true); + m_scene.AddNewSceneObject(object1, false); + + string object1FileName = string.Format( + "{0}_{1:000}-{2:000}-{3:000}__{4}.xml", + part1.Name, + Math.Round(part1.GroupPosition.X), Math.Round(part1.GroupPosition.Y), Math.Round(part1.GroupPosition.Z), + part1.UUID); + string path = "regions/1_1_Unit_test_region/" + ArchiveConstants.OBJECTS_PATH + object1FileName; + tar.WriteFile(path, SceneObjectSerializer.ToXml2Format(object1)); + + tar.Close(); + + + // Delete the current objects, to test that they're loaded from the OAR and didn't + // just remain in the scene. + m_sceneHelpers.SceneManager.ForEachScene(delegate(Scene scene) + { + scene.DeleteAllSceneObjects(); + }); + + // Create a "hole", to test that that the corresponding region isn't loaded from the OAR + m_sceneHelpers.SceneManager.CloseScene(SceneManager.Instance.Scenes[1]); + + + // Check thay the OAR file contains the expected data + + MemoryStream archiveReadStream = new MemoryStream(archiveWriteStream.ToArray()); + + m_scene.EventManager.OnOarFileLoaded += LoadCompleted; + m_oarEvent.Reset(); + m_archiverModule.DearchiveRegion(archiveReadStream); + + m_oarEvent.WaitOne(60000); + + Assert.That(m_lastErrorMessage, Is.Null); + + Assert.AreEqual(3, m_sceneHelpers.SceneManager.Scenes.Count); + + TestLoadedRegion(part1, soundItemName, soundData); + } + + private void TestLoadedRegion(SceneObjectPart part1, string soundItemName, byte[] soundData) + { + SceneObjectPart object1PartLoaded = m_scene.GetSceneObjectPart(part1.Name); + + Assert.That(object1PartLoaded, Is.Not.Null, "object1 was not loaded"); + Assert.That(object1PartLoaded.Name, Is.EqualTo(part1.Name), "object1 names not identical"); + Assert.That(object1PartLoaded.GroupPosition, Is.EqualTo(part1.GroupPosition), "object1 group position not equal"); + + Quaternion qtmp1 = new Quaternion ( + (float)Math.Round(object1PartLoaded.RotationOffset.X,5), + (float)Math.Round(object1PartLoaded.RotationOffset.Y,5), + (float)Math.Round(object1PartLoaded.RotationOffset.Z,5), + (float)Math.Round(object1PartLoaded.RotationOffset.W,5)); + Quaternion qtmp2 = new Quaternion ( + (float)Math.Round(part1.RotationOffset.X,5), + (float)Math.Round(part1.RotationOffset.Y,5), + (float)Math.Round(part1.RotationOffset.Z,5), + (float)Math.Round(part1.RotationOffset.W,5)); + + Assert.That(qtmp1, Is.EqualTo(qtmp2), "object1 rotation offset not equal"); + Assert.That( + object1PartLoaded.OffsetPosition, Is.EqualTo(part1.OffsetPosition), "object1 offset position not equal"); + Assert.That(object1PartLoaded.SitTargetOrientation, Is.EqualTo(part1.SitTargetOrientation)); + Assert.That(object1PartLoaded.SitTargetPosition, Is.EqualTo(part1.SitTargetPosition)); + + TaskInventoryItem loadedSoundItem = object1PartLoaded.Inventory.GetInventoryItems(soundItemName)[0]; + Assert.That(loadedSoundItem, Is.Not.Null, "loaded sound item was null"); + AssetBase loadedSoundAsset = m_scene.AssetService.Get(loadedSoundItem.AssetID.ToString()); + Assert.That(loadedSoundAsset, Is.Not.Null, "loaded sound asset was null"); + Assert.That(loadedSoundAsset.Data, Is.EqualTo(soundData), "saved and loaded sound data do not match"); + + Assert.Greater(m_scene.LandChannel.AllParcels().Count, 0, "incorrect number of parcels"); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/Resources/test-sound.wav b/Tests/OpenSim.Region.CoreModules.Tests/World/Archiver/Tests/Resources/test-sound.wav new file mode 100644 index 0000000000000000000000000000000000000000..b45ee54d354c075cec2c681e057265bee4c8ccef GIT binary patch literal 211648 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 literal 0 HcmV?d00001 diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/LandManagementModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/LandManagementModuleTests.cs new file mode 100644 index 00000000000..c512eac3e8c --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/LandManagementModuleTests.cs @@ -0,0 +1,266 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.World.Land.Tests +{ + public class LandManagementModuleTests : OpenSimTestCase + { + [Test] + public void TestAddLandObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + LandManagementModule lmm = new LandManagementModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject lo = new LandObject(userId, false, scene); + lo.LandData.Name = "lo1"; + lo.SetLandBitmap( + lo.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo = lmm.AddLandObject(lo); + + // TODO: Should add asserts to check that land object was added properly. + + // At the moment, this test just makes sure that we can't add a land object that overlaps the areas that + // the first still holds. + ILandObject lo2 = new LandObject(userId, false, scene); + lo2.SetLandBitmap( + lo2.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo2.LandData.Name = "lo2"; + lo2 = lmm.AddLandObject(lo2); + + { + ILandObject loAtCoord = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord.LandData.LocalID, Is.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(lo.LandData.GlobalID)); + } + + { + ILandObject loAtCoord = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord.LandData.LocalID, Is.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(lo.LandData.GlobalID)); + } + } + + /// + /// Test parcels on region when no land data exists to be loaded. + /// + [Test] + public void TestLoadWithNoParcels() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + ILandObject loAtCoord1 = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord1.LandData.LocalID, Is.Not.EqualTo(0)); + Assert.That(loAtCoord1.LandData.GlobalID, Is.Not.EqualTo(UUID.Zero)); + + ILandObject loAtCoord2 = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord2.LandData.LocalID, Is.EqualTo(loAtCoord1.LandData.LocalID)); + Assert.That(loAtCoord2.LandData.GlobalID, Is.EqualTo(loAtCoord1.LandData.GlobalID)); + } + + /// + /// Test parcels on region when a single parcel already exists but it does not cover the whole region. + /// + [Test] + public void TestLoadWithSinglePartialCoveringParcel() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject originalLo1 = new LandObject(userId, false, scene); + originalLo1.LandData.Name = "lo1"; + originalLo1.SetLandBitmap( + originalLo1.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize / 2)); + + sh.SimDataService.StoreLandObject(originalLo1); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + ILandObject loAtCoord1 = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord1.LandData.Name, Is.EqualTo(originalLo1.LandData.Name)); + Assert.That(loAtCoord1.LandData.GlobalID, Is.EqualTo(originalLo1.LandData.GlobalID)); + + ILandObject loAtCoord2 = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord2.LandData.LocalID, Is.EqualTo(loAtCoord1.LandData.LocalID)); + Assert.That(loAtCoord2.LandData.GlobalID, Is.EqualTo(loAtCoord1.LandData.GlobalID)); + } + + /// + /// Test parcels on region when a single parcel already exists but it does not cover the whole region. + /// + [Test] + public void TestLoadWithMultiplePartialCoveringParcels() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject originalLo1 = new LandObject(userId, false, scene); + originalLo1.LandData.Name = "lo1"; + originalLo1.SetLandBitmap( + originalLo1.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize / 2)); + + sh.SimDataService.StoreLandObject(originalLo1); + + ILandObject originalLo2 = new LandObject(userId, false, scene); + originalLo2.LandData.Name = "lo2"; + originalLo2.SetLandBitmap( + originalLo2.GetSquareLandBitmap( + 0, (int)Constants.RegionSize / 2, (int)Constants.RegionSize, ((int)Constants.RegionSize / 4) * 3)); + + sh.SimDataService.StoreLandObject(originalLo2); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + ILandObject loAtCoord1 = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord1.LandData.Name, Is.EqualTo(originalLo1.LandData.Name)); + Assert.That(loAtCoord1.LandData.GlobalID, Is.EqualTo(originalLo1.LandData.GlobalID)); + + ILandObject loAtCoord2 + = lmm.GetLandObject((int)Constants.RegionSize - 1, (((int)Constants.RegionSize / 4) * 3) - 1); + Assert.That(loAtCoord2.LandData.Name, Is.EqualTo(originalLo2.LandData.Name)); + Assert.That(loAtCoord2.LandData.GlobalID, Is.EqualTo(originalLo2.LandData.GlobalID)); + + ILandObject loAtCoord3 = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord3.LandData.LocalID, Is.Not.EqualTo(loAtCoord1.LandData.LocalID)); + Assert.That(loAtCoord3.LandData.LocalID, Is.Not.EqualTo(loAtCoord2.LandData.LocalID)); + Assert.That(loAtCoord3.LandData.GlobalID, Is.Not.EqualTo(loAtCoord1.LandData.GlobalID)); + Assert.That(loAtCoord3.LandData.GlobalID, Is.Not.EqualTo(loAtCoord2.LandData.GlobalID)); + } + + /// + /// Test parcels on region when whole region is parcelled (which should normally always be the case). + /// + [Test] + public void TestLoad() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneHelpers sh = new SceneHelpers(); + LandManagementModule lmm = new LandManagementModule(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject originalLo1 = new LandObject(userId, false, scene); + originalLo1.LandData.Name = "lo1"; + originalLo1.SetLandBitmap( + originalLo1.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize / 2)); + + sh.SimDataService.StoreLandObject(originalLo1); + + ILandObject originalLo2 = new LandObject(userId, false, scene); + originalLo2.LandData.Name = "lo2"; + originalLo2.SetLandBitmap( + originalLo2.GetSquareLandBitmap(0, (int)Constants.RegionSize / 2, (int)Constants.RegionSize, (int)Constants.RegionSize)); + + sh.SimDataService.StoreLandObject(originalLo2); + + scene.loadAllLandObjectsFromStorage(scene.RegionInfo.RegionID); + + { + ILandObject loAtCoord = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord.LandData.Name, Is.EqualTo(originalLo1.LandData.Name)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(originalLo1.LandData.GlobalID)); + } + + { + ILandObject loAtCoord = lmm.GetLandObject((int)Constants.RegionSize - 1, ((int)Constants.RegionSize - 1)); + Assert.That(loAtCoord.LandData.Name, Is.EqualTo(originalLo2.LandData.Name)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(originalLo2.LandData.GlobalID)); + } + } + + [Test] + public void TestSubdivide() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + LandManagementModule lmm = new LandManagementModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, lmm); + + ILandObject lo = new LandObject(userId, false, scene); + lo.LandData.Name = "lo1"; + lo.SetLandBitmap( + lo.GetSquareLandBitmap(0, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo = lmm.AddLandObject(lo); + + lmm.Subdivide(0, 0, Constants.LandUnit, Constants.LandUnit, userId); + + { + ILandObject loAtCoord = lmm.GetLandObject(0, 0); + Assert.That(loAtCoord.LandData.LocalID, Is.Not.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.Not.EqualTo(lo.LandData.GlobalID)); + } + + { + ILandObject loAtCoord = lmm.GetLandObject(Constants.LandUnit, Constants.LandUnit); + Assert.That(loAtCoord.LandData.LocalID, Is.EqualTo(lo.LandData.LocalID)); + Assert.That(loAtCoord.LandData.GlobalID, Is.EqualTo(lo.LandData.GlobalID)); + } + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/PrimCountModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/PrimCountModuleTests.cs new file mode 100644 index 00000000000..44475deb8e4 --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Land/Tests/PrimCountModuleTests.cs @@ -0,0 +1,376 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.World.Land.Tests +{ + [TestFixture] + public class PrimCountModuleTests : OpenSimTestCase + { + protected UUID m_userId = new UUID("00000000-0000-0000-0000-100000000000"); + protected UUID m_groupId = new UUID("00000000-0000-0000-8888-000000000000"); + protected UUID m_otherUserId = new UUID("99999999-9999-9999-9999-999999999999"); + protected TestScene m_scene; + protected PrimCountModule m_pcm; + + /// + /// A parcel that covers the entire sim except for a 1 unit wide strip on the eastern side. + /// + protected ILandObject m_lo; + + /// + /// A parcel that covers just the eastern strip of the sim. + /// + protected ILandObject m_lo2; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_pcm = new PrimCountModule(); + LandManagementModule lmm = new LandManagementModule(); + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, lmm, m_pcm); + + int xParcelDivider = (int)Constants.RegionSize - 1; + + ILandObject lo = new LandObject(m_userId, false, m_scene); + lo.LandData.Name = "m_lo"; + lo.SetLandBitmap( + lo.GetSquareLandBitmap(0, 0, xParcelDivider, (int)Constants.RegionSize)); + m_lo = lmm.AddLandObject(lo); + + ILandObject lo2 = new LandObject(m_userId, false, m_scene); + lo2.SetLandBitmap( + lo2.GetSquareLandBitmap(xParcelDivider, 0, (int)Constants.RegionSize, (int)Constants.RegionSize)); + lo2.LandData.Name = "m_lo2"; + m_lo2 = lmm.AddLandObject(lo2); + } + + /// + /// Test that counts before we do anything are correct. + /// + [Test] + public void TestInitialCounts() + { + IPrimCounts pc = m_lo.PrimCounts; + + Assert.That(pc.Owner, Is.EqualTo(0)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(0)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(0)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(0)); + } + + /// + /// Test count after a parcel owner owned object is added. + /// + [Test] + public void TestAddOwnerObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IPrimCounts pc = m_lo.PrimCounts; + + SceneObjectGroup sog = SceneHelpers.CreateSceneObject(3, m_userId, "a", 0x01); + m_scene.AddNewSceneObject(sog, false); + + Assert.That(pc.Owner, Is.EqualTo(3)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(3)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(3)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(3)); + + // Add a second object and retest + SceneObjectGroup sog2 = SceneHelpers.CreateSceneObject(2, m_userId, "b", 0x10); + m_scene.AddNewSceneObject(sog2, false); + + Assert.That(pc.Owner, Is.EqualTo(5)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(5)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(5)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(5)); + } + + /// + /// Test count after a parcel owner owned copied object is added. + /// + [Test] + public void TestCopyOwnerObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IPrimCounts pc = m_lo.PrimCounts; + + SceneObjectGroup sog = SceneHelpers.CreateSceneObject(3, m_userId, "a", 0x01); + m_scene.AddNewSceneObject(sog, false); + m_scene.SceneGraph.DuplicateObject(sog.LocalId, Vector3.Zero, m_userId, UUID.Zero, Quaternion.Identity, false); + + Assert.That(pc.Owner, Is.EqualTo(6)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(6)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(6)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(6)); + } + + /// + /// Test that parcel counts update correctly when an object is moved between parcels, where that movement + /// is not done directly by the user/ + /// + [Test] + public void TestMoveOwnerObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectGroup sog = SceneHelpers.CreateSceneObject(3, m_userId, "a", 0x01); + m_scene.AddNewSceneObject(sog, false); + SceneObjectGroup sog2 = SceneHelpers.CreateSceneObject(2, m_userId, "b", 0x10); + m_scene.AddNewSceneObject(sog2, false); + + // Move the first scene object to the eastern strip parcel + sog.AbsolutePosition = new Vector3(254, 2, 2); + + IPrimCounts pclo1 = m_lo.PrimCounts; + + Assert.That(pclo1.Owner, Is.EqualTo(2)); + Assert.That(pclo1.Group, Is.EqualTo(0)); + Assert.That(pclo1.Others, Is.EqualTo(0)); + Assert.That(pclo1.Total, Is.EqualTo(2)); + Assert.That(pclo1.Selected, Is.EqualTo(0)); + Assert.That(pclo1.Users[m_userId], Is.EqualTo(2)); + Assert.That(pclo1.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pclo1.Simulator, Is.EqualTo(5)); + + IPrimCounts pclo2 = m_lo2.PrimCounts; + + Assert.That(pclo2.Owner, Is.EqualTo(3)); + Assert.That(pclo2.Group, Is.EqualTo(0)); + Assert.That(pclo2.Others, Is.EqualTo(0)); + Assert.That(pclo2.Total, Is.EqualTo(3)); + Assert.That(pclo2.Selected, Is.EqualTo(0)); + Assert.That(pclo2.Users[m_userId], Is.EqualTo(3)); + Assert.That(pclo2.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pclo2.Simulator, Is.EqualTo(5)); + + // Now move it back again + sog.AbsolutePosition = new Vector3(2, 2, 2); + + Assert.That(pclo1.Owner, Is.EqualTo(5)); + Assert.That(pclo1.Group, Is.EqualTo(0)); + Assert.That(pclo1.Others, Is.EqualTo(0)); + Assert.That(pclo1.Total, Is.EqualTo(5)); + Assert.That(pclo1.Selected, Is.EqualTo(0)); + Assert.That(pclo1.Users[m_userId], Is.EqualTo(5)); + Assert.That(pclo1.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pclo1.Simulator, Is.EqualTo(5)); + + Assert.That(pclo2.Owner, Is.EqualTo(0)); + Assert.That(pclo2.Group, Is.EqualTo(0)); + Assert.That(pclo2.Others, Is.EqualTo(0)); + Assert.That(pclo2.Total, Is.EqualTo(0)); + Assert.That(pclo2.Selected, Is.EqualTo(0)); + Assert.That(pclo2.Users[m_userId], Is.EqualTo(0)); + Assert.That(pclo2.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pclo2.Simulator, Is.EqualTo(5)); + } + + /// + /// Test count after a parcel owner owned object is removed. + /// + [Test] + public void TestRemoveOwnerObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IPrimCounts pc = m_lo.PrimCounts; + + m_scene.AddNewSceneObject(SceneHelpers.CreateSceneObject(1, m_userId, "a", 0x1), false); + SceneObjectGroup sogToDelete = SceneHelpers.CreateSceneObject(3, m_userId, "b", 0x10); + m_scene.AddNewSceneObject(sogToDelete, false); + m_scene.DeleteSceneObject(sogToDelete, false); + + Assert.That(pc.Owner, Is.EqualTo(1)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(1)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(1)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(1)); + } + + [Test] + public void TestAddGroupObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + m_lo.DeedToGroup(m_groupId); + + IPrimCounts pc = m_lo.PrimCounts; + + SceneObjectGroup sog = SceneHelpers.CreateSceneObject(3, m_otherUserId, "a", 0x01); + sog.GroupID = m_groupId; + m_scene.AddNewSceneObject(sog, false); + + Assert.That(pc.Owner, Is.EqualTo(0)); + Assert.That(pc.Group, Is.EqualTo(3)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(3)); + Assert.That(pc.Selected, Is.EqualTo(0)); + + // Is this desired behaviour? Not totally sure. + Assert.That(pc.Users[m_userId], Is.EqualTo(0)); + Assert.That(pc.Users[m_groupId], Is.EqualTo(0)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(3)); + + Assert.That(pc.Simulator, Is.EqualTo(3)); + } + + /// + /// Test count after a parcel owner owned object is removed. + /// + [Test] + public void TestRemoveGroupObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + m_lo.DeedToGroup(m_groupId); + + IPrimCounts pc = m_lo.PrimCounts; + + SceneObjectGroup sogToKeep = SceneHelpers.CreateSceneObject(1, m_userId, "a", 0x1); + sogToKeep.GroupID = m_groupId; + m_scene.AddNewSceneObject(sogToKeep, false); + + SceneObjectGroup sogToDelete = SceneHelpers.CreateSceneObject(3, m_userId, "b", 0x10); + m_scene.AddNewSceneObject(sogToDelete, false); + m_scene.DeleteSceneObject(sogToDelete, false); + + Assert.That(pc.Owner, Is.EqualTo(0)); + Assert.That(pc.Group, Is.EqualTo(1)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(1)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(1)); + Assert.That(pc.Users[m_groupId], Is.EqualTo(0)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(1)); + } + + [Test] + public void TestAddOthersObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IPrimCounts pc = m_lo.PrimCounts; + + SceneObjectGroup sog = SceneHelpers.CreateSceneObject(3, m_otherUserId, "a", 0x01); + m_scene.AddNewSceneObject(sog, false); + + Assert.That(pc.Owner, Is.EqualTo(0)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(3)); + Assert.That(pc.Total, Is.EqualTo(3)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(0)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(3)); + Assert.That(pc.Simulator, Is.EqualTo(3)); + } + + [Test] + public void TestRemoveOthersObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IPrimCounts pc = m_lo.PrimCounts; + + m_scene.AddNewSceneObject(SceneHelpers.CreateSceneObject(1, m_otherUserId, "a", 0x1), false); + SceneObjectGroup sogToDelete = SceneHelpers.CreateSceneObject(3, m_otherUserId, "b", 0x10); + m_scene.AddNewSceneObject(sogToDelete, false); + m_scene.DeleteSceneObject(sogToDelete, false); + + Assert.That(pc.Owner, Is.EqualTo(0)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(1)); + Assert.That(pc.Total, Is.EqualTo(1)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(0)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(1)); + Assert.That(pc.Simulator, Is.EqualTo(1)); + } + + /// + /// Test the count is correct after is has been tainted. + /// + [Test] + public void TestTaint() + { + TestHelpers.InMethod(); + IPrimCounts pc = m_lo.PrimCounts; + + SceneObjectGroup sog = SceneHelpers.CreateSceneObject(3, m_userId, "a", 0x01); + m_scene.AddNewSceneObject(sog, false); + + m_pcm.TaintPrimCount(); + + Assert.That(pc.Owner, Is.EqualTo(3)); + Assert.That(pc.Group, Is.EqualTo(0)); + Assert.That(pc.Others, Is.EqualTo(0)); + Assert.That(pc.Total, Is.EqualTo(3)); + Assert.That(pc.Selected, Is.EqualTo(0)); + Assert.That(pc.Users[m_userId], Is.EqualTo(3)); + Assert.That(pc.Users[m_otherUserId], Is.EqualTo(0)); + Assert.That(pc.Simulator, Is.EqualTo(3)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Media/Moap/Tests/MoapTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Media/Moap/Tests/MoapTests.cs new file mode 100644 index 00000000000..6165dd07bcf --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Media/Moap/Tests/MoapTests.cs @@ -0,0 +1,92 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.World.Media.Moap.Tests +{ + [TestFixture] + public class MoapTests : OpenSimTestCase + { + protected TestScene m_scene; + protected MoapModule m_module; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_module = new MoapModule(); + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, m_module); + } + + [Test] + public void TestClearMediaUrl() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + MediaEntry me = new MediaEntry(); + + m_module.SetMediaEntry(part, 1, me); + m_module.ClearMediaEntry(part, 1); + + Assert.That(part.Shape.Media[1], Is.EqualTo(null)); + + // Although we've cleared one face, other faces may still be present. So we need to check for an + // update media url version + Assert.That(part.MediaUrl, Is.EqualTo("x-mv:0000000002/" + UUID.Zero)); + + // By changing media flag to false, the face texture once again becomes identical to the DefaultTexture. + // Therefore, when libOMV reserializes it, it disappears and we are left with no face texture in this slot. + // Not at all confusing, eh? + Assert.That(part.Shape.Textures.FaceTextures[1], Is.Null); + } + + [Test] + public void TestSetMediaUrl() + { + TestHelpers.InMethod(); + + string homeUrl = "opensimulator.org"; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + MediaEntry me = new MediaEntry() { HomeURL = homeUrl }; + + m_module.SetMediaEntry(part, 1, me); + + Assert.That(part.Shape.Media[1].HomeURL, Is.EqualTo(homeUrl)); + Assert.That(part.MediaUrl, Is.EqualTo("x-mv:0000000001/" + UUID.Zero)); + Assert.That(part.Shape.Textures.FaceTextures[1].MediaFlags, Is.True); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Serialiser/Tests/SerialiserTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Serialiser/Tests/SerialiserTests.cs new file mode 100644 index 00000000000..4be4779133d --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Serialiser/Tests/SerialiserTests.cs @@ -0,0 +1,881 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSim Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Xml; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.World.Serialiser.Tests +{ + [TestFixture] + public class SerialiserTests : OpenSimTestCase + { + private const string ObjectRootPartStubXml = +@" + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + e6a5a05e-e8cc-4816-8701-04165e335790 + 1 + + 0 + e6a5a05e-e8cc-4816-8701-04165e335790 + 2698615125 + PrimMyRide + 0 + false + 1099511628032000 + 0 + 147.2392.69822.78084 + 000 + -4.371139E-08-1-4.371139E-080 + 000 + 000 + 000 + 000 + + + + + + 0 + 0 + + 1 + AAAAAAAAERGZmQAAAAAABQCVlZUAAAAAQEAAAABAQAAAAAAAAAAAAAAAAAAAAA== + AA== + 0 + 16 + 0 + 0 + 0 + 100 + 100 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 10100.5 + 0 + Square + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 10100.5 + 0 + 0001 + 000 + 000 + 0001 + 0 + 1211330445 + 0 + 0 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + a6dacf01-4636-4bb9-8a97-30609438af9d + a6dacf01-4636-4bb9-8a97-30609438af9d + 2147483647 + 2147483647 + 0 + 0 + 2147483647 + None + 00000000-0000-0000-0000-000000000000 + 0 + + + + MyNamespace + + MyStore + + the answer + 42 + + + + + + + "; + + private const string ObjectWithNoOtherPartsXml = ObjectRootPartStubXml + +@" + +"; + + private const string ObjectWithOtherPartsXml = ObjectRootPartStubXml + +@" + + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + 9958feb1-02a6-49e4-a4ce-eba6f578ee13 + 3 + 9958feb1-02a6-49e4-a4ce-eba6f578ee13 + 1154704500 + Alien Head 1 + 3 + false + false + 21990232560640000 + 0 + 125.5655127.34622.48036 + -0.21719360.10839840.0009994507 + -0.51221060.4851225-0.49574540.5064908 + 000 + 000 + 000 + (No Description) + 000255 + + + + 253 + 0 + + 5 + Vw3dpvgTRUOiIUOGsnpWlAB/f38AAAAAgL8AAACAPwAAAAAAAAAF4ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AA== + 0 + 32 + 0 + 0 + 0 + 100 + 100 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 9 + 0 + HalfCircle + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 0.11481950.01438910.02768878 + 0001 + 000 + 000 + 0001 + 1154704499 + 1256611042 + 0 + 10 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 647168 + 647168 + 0 + 0 + 581632 + None + 00000000-0000-0000-0000-000000000000 + 0 + 000 + + + -2 + -2 + -2 + -2 + -2 + + + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + 674b6b86-f5aa-439a-8e00-0d75bc08c80a + 3 + 674b6b86-f5aa-439a-8e00-0d75bc08c80a + 1154704501 + Alien Head 2 + 3 + false + false + 21990232560640000 + 0 + 125.5655127.34622.48036 + -0.24909970.085201260.0009002686 + -0.47653680.5194498-0.53013720.4712104 + 000 + 000 + 000 + (No Description) + 000255 + + + + 252 + 0 + + 0 + Vw3dpvgTRUOiIUOGsnpWlAB/f38AAAAAgL8AAACAPwAAAAAAAAAF4ABAAAAAAAAAAAAAAAAAAAAAAAAAAAAA + AA== + 0 + 32 + 0 + 0 + 0 + 100 + 150 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 9 + 0 + Circle + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 0.035743850.059580320.04764182 + 0001 + 000 + 000 + 0001 + 1154704499 + 1256611042 + 0 + 10 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 7b2022f0-5f19-488c-b7e5-829d8f96b448 + 647168 + 647168 + 0 + 0 + 581632 + None + 00000000-0000-0000-0000-000000000000 + 0 + 000 + + + -2 + -2 + -2 + -2 + -2 + + + +"; + + private const string ObjectWithBadFloatsXml = @" + + + + false + a6dacf01-4636-4bb9-8a97-30609438af9d + e6a5a05e-e8cc-4816-8701-04165e335790 + 1 + + 0 + e6a5a05e-e8cc-4816-8701-04165e335790 + 2698615125 + NaughtyPrim + 0 + false + 1099511628032000 + 0 + 147.2392.69822.78084 + 000 + -4.371139E-08-1-4.371139E-080 + 000 + 000 + 000 + 000 + + + + + + 0 + 0 + + 1 + AAAAAAAAERGZmQAAAAAABQCVlZUAAAAAQEAAAABAQAAAAAAAAAAAAAAAAAAAAA== + AA== + 0 + 16 + 0 + 0 + 0 + 100 + 100 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 10100.5 + 0 + Square + Same + 00000000-0000-0000-0000-000000000000 + 0 + 0 + 0,5 + yo mamma + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 1 + 0 + 0 + 0 + 1 + false + false + false + + 10100.5 + 0 + 0001 + 000 + 000 + 0001 + 0 + 1211330445 + 0 + 0 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + a6dacf01-4636-4bb9-8a97-30609438af9d + a6dacf01-4636-4bb9-8a97-30609438af9d + 2147483647 + 2147483647 + 0 + 0 + 2147483647 + None + 00000000-0000-0000-0000-000000000000 + 0 + + + + "; + + private const string ObjectWithNoPartsXml2 = @" + + + b46ef588-411e-4a8b-a284-d7dcfe8e74ef + 9be68fdd-f740-4a0f-9675-dfbbb536b946 + 0 + + 0 + 9be68fdd-f740-4a0f-9675-dfbbb536b946 + 720005 + PrimFun + 0 + 1099511628032000 + 0 + 153.9854121.490862.21781 + 000 + 0001 + 000 + 000 + 000 + 000 + + + + + + 0 + 0 + + 0 + 16 + 0 + 0 + 0 + 200 + 200 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 9 + 0 + 0 + 0 + 1.2831315.9038584.266288 + 0 + Circle + Same + 0 + iVVnRyTLQ+2SC0fK7RVGXwJ6yc/SU4RDA5nhJbLUw3R1AAAAAAAAaOw8QQOhPSRAAKE9JEAAAAAAAAAAAAAAAAAAAAA= + AA== + + 1.2831315.9038584.266288 + 0 + 0001 + 000 + 000 + 0010 + 0 + 1216066902 + 0 + 0 + 0 + 0 + 00000000-0000-0000-0000-000000000000 + b46ef588-411e-4a8b-a284-d7dcfe8e74ef + b46ef588-411e-4a8b-a284-d7dcfe8e74ef + 2147483647 + 2147483647 + 0 + 0 + 2147483647 + None + + + + MyNamespace + + MyStore + + last words + Rosebud + + + + + + 00000000-0000-0000-0000-000000000000 + + + "; + + protected Scene m_scene; + protected SerialiserModule m_serialiserModule; + + [OneTimeSetUp] + public void Init() + { + m_serialiserModule = new SerialiserModule(); + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, m_serialiserModule); + } + + [Test] + public void TestDeserializeXmlObjectWithNoOtherParts() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(ObjectWithNoOtherPartsXml); + SceneObjectPart rootPart = so.RootPart; + + Assert.That(rootPart.UUID, Is.EqualTo(new UUID("e6a5a05e-e8cc-4816-8701-04165e335790"))); + Assert.That(rootPart.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(rootPart.Name, Is.EqualTo("PrimMyRide")); + OSDMap store = rootPart.DynAttrs.GetStore("MyNamespace", "MyStore"); + Assert.AreEqual(42, store["the answer"].AsInteger()); + + // TODO: Check other properties + } + + [Test] + public void TestDeserializeXmlObjectWithOtherParts() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(ObjectWithOtherPartsXml); + SceneObjectPart[] parts = so.Parts; + Assert.AreEqual(3, so.Parts.Length); + + { + SceneObjectPart part = parts[0]; + + Assert.That(part.UUID, Is.EqualTo(new UUID("e6a5a05e-e8cc-4816-8701-04165e335790"))); + Assert.That(part.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(part.Name, Is.EqualTo("PrimMyRide")); + OSDMap store = part.DynAttrs.GetStore("MyNamespace", "MyStore"); + Assert.AreEqual(42, store["the answer"].AsInteger()); + } + + { + SceneObjectPart part = parts[1]; + + Assert.That(part.UUID, Is.EqualTo(new UUID("9958feb1-02a6-49e4-a4ce-eba6f578ee13"))); + Assert.That(part.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(part.Name, Is.EqualTo("Alien Head 1")); + } + + { + SceneObjectPart part = parts[2]; + + Assert.That(part.UUID, Is.EqualTo(new UUID("674b6b86-f5aa-439a-8e00-0d75bc08c80a"))); + Assert.That(part.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(part.Name, Is.EqualTo("Alien Head 2")); + } + + // TODO: Check other properties + } + + [Test] + public void TestDeserializeBadFloatsXml() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectGroup so = SceneObjectSerializer.FromOriginalXmlFormat(ObjectWithBadFloatsXml); + SceneObjectPart rootPart = so.RootPart; + + Assert.That(rootPart.UUID, Is.EqualTo(new UUID("e6a5a05e-e8cc-4816-8701-04165e335790"))); + Assert.That(rootPart.CreatorID, Is.EqualTo(new UUID("a6dacf01-4636-4bb9-8a97-30609438af9d"))); + Assert.That(rootPart.Name, Is.EqualTo("NaughtyPrim")); + + // This terminates the deserialization earlier if couldn't be parsed. + // TODO: Need to address this + Assert.That(rootPart.GroupPosition.X, Is.EqualTo(147.23f)); + + Assert.That(rootPart.Shape.PathCurve, Is.EqualTo(16)); + + // Defaults for bad parses + Assert.That(rootPart.Shape.FlexiTension, Is.EqualTo(0)); + Assert.That(rootPart.Shape.FlexiDrag, Is.EqualTo(0)); + + // TODO: Check other properties + } + + [Test] + public void TestSerializeXml() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string rpName = "My Little Donkey"; + UUID rpUuid = UUID.Parse("00000000-0000-0000-0000-000000000964"); + UUID rpCreatorId = UUID.Parse("00000000-0000-0000-0000-000000000915"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); +// Vector3 groupPosition = new Vector3(10, 20, 30); +// Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); +// Vector3 offsetPosition = new Vector3(5, 10, 15); + + SceneObjectPart rp = new SceneObjectPart(); + rp.UUID = rpUuid; + rp.Name = rpName; + rp.CreatorID = rpCreatorId; + rp.Shape = shape; + + string daNamespace = "MyNamespace"; + string daStoreName = "MyStore"; + string daKey = "foo"; + string daValue = "bar"; + OSDMap myStore = new OSDMap(); + myStore.Add(daKey, daValue); + rp.DynAttrs = new DAMap(); + rp.DynAttrs.SetStore(daNamespace, daStoreName, myStore); + + SceneObjectGroup so = new SceneObjectGroup(rp); + + // Need to add the object to the scene so that the request to get script state succeeds + m_scene.AddSceneObject(so); + + string xml = SceneObjectSerializer.ToOriginalXmlFormat(so); + + XmlTextReader xtr = new XmlTextReader(new StringReader(xml)); + xtr.DtdProcessing = DtdProcessing.Ignore; + xtr.ReadStartElement("SceneObjectGroup"); + xtr.ReadStartElement("RootPart"); + xtr.ReadStartElement("SceneObjectPart"); + + UUID uuid = UUID.Zero; + string name = null; + UUID creatorId = UUID.Zero; + DAMap daMap = null; + + while (xtr.Read() && xtr.Name != "SceneObjectPart") + { + if (xtr.NodeType != XmlNodeType.Element) + continue; + + switch (xtr.Name) + { + case "UUID": + xtr.ReadStartElement("UUID"); + try + { + uuid = UUID.Parse(xtr.ReadElementString("UUID")); + xtr.ReadEndElement(); + } + catch { } // ignore everything but ... + break; + case "Name": + name = xtr.ReadElementContentAsString(); + break; + case "CreatorID": + xtr.ReadStartElement("CreatorID"); + creatorId = UUID.Parse(xtr.ReadElementString("UUID")); + xtr.ReadEndElement(); + break; + case "DynAttrs": + daMap = new DAMap(); + daMap.ReadXml(xtr); + break; + } + } + + xtr.ReadEndElement(); + xtr.ReadEndElement(); + xtr.ReadStartElement("OtherParts"); + xtr.ReadEndElement(); + xtr.Close(); + + // TODO: More checks + Assert.That(uuid, Is.EqualTo(rpUuid)); + Assert.That(name, Is.EqualTo(rpName)); + Assert.That(creatorId, Is.EqualTo(rpCreatorId)); + Assert.NotNull(daMap); + Assert.AreEqual(daValue, daMap.GetStore(daNamespace, daStoreName)[daKey].AsString()); + } + + [Test] + public void TestDeserializeXml2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectGroup so = m_serialiserModule.DeserializeGroupFromXml2(ObjectWithNoPartsXml2); + SceneObjectPart rootPart = so.RootPart; + + Assert.That(rootPart.UUID, Is.EqualTo(new UUID("9be68fdd-f740-4a0f-9675-dfbbb536b946"))); + Assert.That(rootPart.CreatorID, Is.EqualTo(new UUID("b46ef588-411e-4a8b-a284-d7dcfe8e74ef"))); + Assert.That(rootPart.Name, Is.EqualTo("PrimFun")); + OSDMap store = rootPart.DynAttrs.GetStore("MyNamespace", "MyStore"); + Assert.AreEqual("Rosebud", store["last words"].AsString()); + + // TODO: Check other properties + } + + [Test] + public void TestSerializeXml2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string rpName = "My Little Pony"; + UUID rpUuid = UUID.Parse("00000000-0000-0000-0000-000000000064"); + UUID rpCreatorId = UUID.Parse("00000000-0000-0000-0000-000000000015"); + PrimitiveBaseShape shape = PrimitiveBaseShape.CreateSphere(); +// Vector3 groupPosition = new Vector3(10, 20, 30); +// Quaternion rotationOffset = new Quaternion(20, 30, 40, 50); +// Vector3 offsetPosition = new Vector3(5, 10, 15); + + SceneObjectPart rp = new SceneObjectPart(); + rp.UUID = rpUuid; + rp.Name = rpName; + rp.CreatorID = rpCreatorId; + rp.Shape = shape; + + string daNamespace = "MyNamespace"; + string daStoreName = "MyStore"; + string daKey = "foo"; + string daValue = "bar"; + OSDMap myStore = new OSDMap(); + myStore.Add(daKey, daValue); + rp.DynAttrs = new DAMap(); + rp.DynAttrs.SetStore(daNamespace, daStoreName, myStore); + + SceneObjectGroup so = new SceneObjectGroup(rp); + + // Need to add the object to the scene so that the request to get script state succeeds + m_scene.AddSceneObject(so); + + Dictionary options = new Dictionary(); + options["old-guids"] = true; + string xml2 = m_serialiserModule.SerializeGroupToXml2(so, options); + + XmlTextReader xtr = new XmlTextReader(new StringReader(xml2)); + xtr.DtdProcessing = DtdProcessing.Ignore; + xtr.ReadStartElement("SceneObjectGroup"); + xtr.ReadStartElement("SceneObjectPart"); + + UUID uuid = UUID.Zero; + string name = null; + UUID creatorId = UUID.Zero; + DAMap daMap = null; + + while (xtr.Read() && xtr.Name != "SceneObjectPart") + { + if (xtr.NodeType != XmlNodeType.Element) + continue; + + switch (xtr.Name) + { + case "UUID": + xtr.ReadStartElement("UUID"); + uuid = UUID.Parse(xtr.ReadElementString("Guid")); + xtr.ReadEndElement(); + break; + case "Name": + name = xtr.ReadElementContentAsString(); + break; + case "CreatorID": + xtr.ReadStartElement("CreatorID"); + creatorId = UUID.Parse(xtr.ReadElementString("Guid")); + xtr.ReadEndElement(); + break; + case "DynAttrs": + daMap = new DAMap(); + daMap.ReadXml(xtr); + break; + } + } + + xtr.ReadEndElement(); + xtr.ReadStartElement("OtherParts"); + xtr.ReadEndElement(); + xtr.Close(); + + // TODO: More checks + Assert.That(uuid, Is.EqualTo(rpUuid)); + Assert.That(name, Is.EqualTo(rpName)); + Assert.That(creatorId, Is.EqualTo(rpCreatorId)); + Assert.NotNull(daMap); + Assert.AreEqual(daValue, daMap.GetStore(daNamespace, daStoreName)[daKey].AsString()); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainModuleTests.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainModuleTests.cs new file mode 100644 index 00000000000..9a679662f3b --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainModuleTests.cs @@ -0,0 +1,75 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Terrain; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.Terrain.Tests +{ + public class TerrainModuleTests : OpenSimTestCase + { + [Test] + public void TestTerrainFill() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + //UUID userId = TestHelpers.ParseTail(0x1); + + TerrainModule tm = new TerrainModule(); + Scene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, tm); + + // Fillheight of 30 + { + float fillHeight = 30; + + tm.InterfaceFillTerrain(new object[] { fillHeight }); + + float height = scene.Heightmap[128, 128]; + + Assert.AreEqual(fillHeight, height); + } + + // Max fillheight of 30 + // According to http://wiki.secondlife.com/wiki/Tips_for_Creating_Heightfields_and_Details_on_Terrain_RAW_Files#Notes_for_Creating_Height_Field_Maps_for_Second_Life + { + float fillHeight = 508; + + tm.InterfaceFillTerrain(new object[] { fillHeight }); + + float height = scene.Heightmap[128, 128]; + + Assert.AreEqual(fillHeight, height); + } + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainTest.cs b/Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainTest.cs new file mode 100644 index 00000000000..e5e2e5ed17c --- /dev/null +++ b/Tests/OpenSim.Region.CoreModules.Tests/World/Terrain/Tests/TerrainTest.cs @@ -0,0 +1,119 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Terrain.PaintBrushes; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.CoreModules.World.Terrain.Tests +{ + [TestFixture] + public class TerrainTest : OpenSimTestCase + { + [Test] + public void BrushTest() + { + int midRegion = (int)Constants.RegionSize / 2; + + // Create a mask that covers only the left half of the region + bool[,] allowMask = new bool[(int)Constants.RegionSize, 256]; + int x; + int y; + for (x = 0; x < midRegion; x++) + { + for (y = 0; y < (int)Constants.RegionSize; y++) + { + allowMask[x,y] = true; + } + } + + // + // Test RaiseSphere + // + TerrainChannel map = new TerrainChannel((int)Constants.RegionSize, (int)Constants.RegionSize); + ITerrainPaintableEffect effect = new RaiseSphere(); + + effect.PaintEffect(map, allowMask, midRegion, midRegion, -1.0f, 5, 6.0f, + 0, midRegion - 1,0, (int)Constants.RegionSize -1); + Assert.That(map[127, midRegion] > 0.0, "Raise brush should raising value at this point (127,128)."); + Assert.That(map[124, midRegion] > 0.0, "Raise brush should raising value at this point (124,128)."); + Assert.That(map[120, midRegion] == 0.0, "Raise brush should not change value at this point (120,128)."); + Assert.That(map[128, midRegion] == 0.0, "Raise brush should not change value at this point (128,128)."); +// Assert.That(map[0, midRegion] == 0.0, "Raise brush should not change value at this point (0,128)."); + // + // Test LowerSphere + // + map = new TerrainChannel((int)Constants.RegionSize, (int)Constants.RegionSize); + for (x=0; x= 0.0, "Lower should not lowering value below 0.0 at this point (127,128)."); + Assert.That(map[127, midRegion] == 0.0, "Lower brush should lowering value to 0.0 at this point (127,128)."); + Assert.That(map[125, midRegion] < 1.0, "Lower brush should lowering value at this point (124,128)."); + Assert.That(map[120, midRegion] == 1.0, "Lower brush should not change value at this point (120,128)."); + Assert.That(map[128, midRegion] == 1.0, "Lower brush should not change value at this point (128,128)."); +// Assert.That(map[0, midRegion] == 1.0, "Lower brush should not change value at this point (0,128)."); + } + + [Test] + public void TerrainChannelTest() + { + TerrainChannel x = new TerrainChannel((int)Constants.RegionSize, (int)Constants.RegionSize); + Assert.That(x[0, 0] == 0.0, "Terrain not initialising correctly."); + + x[0, 0] = 1.0f; + Assert.That(x[0, 0] == 1.0, "Terrain not setting values correctly."); + + x[0, 0] = 0; + x[0, 0] += 5.0f; + x[0, 0] -= 1.0f; + Assert.That(x[0, 0] == 4.0f, "Terrain addition/subtraction error."); + + x[0, 0] = 1.0f; + float[] floatsExport = x.GetFloatsSerialised(); + Assert.That(floatsExport[0] == 1.0f, "Export to float[] not working correctly."); + + x[0, 0] = 1.0f; + Assert.That(x.Tainted(0, 0), "Terrain channel tainting not working correctly."); + + TerrainChannel y = x.Copy(); + Assert.That(!ReferenceEquals(x, y), "Terrain copy not duplicating correctly."); + Assert.That(!ReferenceEquals(x.GetDoubles(), y.GetDoubles()), "Terrain array not duplicating correctly."); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/GlobalUsings.cs b/Tests/OpenSim.Region.Framework.Tests/GlobalUsings.cs new file mode 100644 index 00000000000..91743bbbbb3 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; diff --git a/Tests/OpenSim.Region.Framework.Tests/OpenSim.Region.Framework.Tests.csproj b/Tests/OpenSim.Region.Framework.Tests/OpenSim.Region.Framework.Tests.csproj new file mode 100644 index 00000000000..10657134910 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/OpenSim.Region.Framework.Tests.csproj @@ -0,0 +1,78 @@ + + + + net8.0 + enable + disable + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + ..\..\bin\Nini.dll + False + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/EntityManagerTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/EntityManagerTests.cs new file mode 100644 index 00000000000..e3a7abdf686 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/EntityManagerTests.cs @@ -0,0 +1,185 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Text; + +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + public class EntityManagerTests : OpenSimTestCase + { + static public Random random; + SceneObjectGroup found; + Scene scene = new SceneHelpers().SetupScene(); + + [Fact] + public void T010_AddObjects() + { + TestHelpers.InMethod(); + + random = new Random(); + SceneObjectGroup found; + EntityManager entman = new EntityManager(); + SceneObjectGroup sog = NewSOG(); + UUID obj1 = sog.UUID; + uint li1 = sog.LocalId; + entman.Add(sog); + sog = NewSOG(); + UUID obj2 = sog.UUID; + uint li2 = sog.LocalId; + entman.Add(sog); + + found = (SceneObjectGroup)entman[obj1]; + found.UUID.Should().Be(obj1); + //Assert.That(found.UUID ,Is.EqualTo(obj1)); + + found = (SceneObjectGroup)entman[li1]; + found.UUID.Should().Be(obj1); + //Assert.That(found.UUID ,Is.EqualTo(obj1)); + + found = (SceneObjectGroup)entman[obj2]; + found.UUID.Should().Be(obj2); + //Assert.That(found.UUID ,Is.EqualTo(obj2)); + + found = (SceneObjectGroup)entman[li2]; + found.UUID.Should().Be(obj2); + //Assert.That(found.UUID ,Is.EqualTo(obj2)); + + entman.Remove(obj1); + entman.Remove(li2); + + entman.ContainsKey(obj1).Should().BeFalse(); + entman.ContainsKey(li1).Should().BeFalse(); + entman.ContainsKey(obj2).Should().BeFalse(); + entman.ContainsKey(li2).Should().BeFalse(); + // Assert.That(entman.ContainsKey(obj1), Is.False); + // Assert.That(entman.ContainsKey(li1), Is.False); + // Assert.That(entman.ContainsKey(obj2), Is.False); + // Assert.That(entman.ContainsKey(li2), Is.False); + } + + [Fact] + public void T011_ThreadAddRemoveTest() + { + TestHelpers.InMethod(); + + // This test adds and removes with mutiple threads, attempting to break the + // uuid and localid dictionary coherence. + EntityManager entman = new EntityManager(); + SceneObjectGroup sog = NewSOG(); + for (int j=0; j<20; j++) + { + List trdlist = new List(); + + for (int i=0; i<4; i++) + { + // Adds scene object + NewTestThreads test = new NewTestThreads(entman,sog); + Thread start = new Thread(new ThreadStart(test.TestAddSceneObject)); + start.Start(); + trdlist.Add(start); + + // Removes it + test = new NewTestThreads(entman,sog); + start = new Thread(new ThreadStart(test.TestRemoveSceneObject)); + start.Start(); + trdlist.Add(start); + } + + foreach (Thread thread in trdlist) + { + thread.Join(); + } + + if (entman.ContainsKey(sog.UUID) || entman.ContainsKey(sog.LocalId)) { + found = (SceneObjectGroup)entman[sog.UUID]; + found.UUID.Should().Be(sog.UUID); + //Assert.That(found.UUID,Is.EqualTo(sog.UUID)); + found = (SceneObjectGroup)entman[sog.LocalId]; + found.UUID.Should().Be(sog.UUID); + //Assert.That(found.UUID,Is.EqualTo(sog.UUID)); + } + } + } + + private SceneObjectGroup NewSOG() + { + SceneObjectPart sop = new SceneObjectPart(UUID.Random(), PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero); + sop.Name = RandomName(); + sop.Description = sop.Name; + sop.Text = RandomName(); + sop.SitName = RandomName(); + sop.TouchName = RandomName(); + sop.Flags |= PrimFlags.Phantom; + + SceneObjectGroup sog = new SceneObjectGroup(sop); + scene.AddNewSceneObject(sog, false); + + return sog; + } + + private static string RandomName() + { + StringBuilder name = new StringBuilder(); + int size = random.Next(40,80); + char ch ; + for (int i=0; i + /// Basic scene object tests (create, read and delete but not update). + /// + public class SceneObjectBasicTests : OpenSimTestCase + { +// [TearDown] +// public void TearDown() +// { +// Console.WriteLine("TearDown"); +// GC.Collect(); +// Thread.Sleep(3000); +// } + +// public class GcNotify +// { +// public static AutoResetEvent gcEvent = new AutoResetEvent(false); +// private static bool _initialized = false; +// +// public static void Initialize() +// { +// if (!_initialized) +// { +// _initialized = true; +// new GcNotify(); +// } +// } +// +// private GcNotify(){} +// +// ~GcNotify() +// { +// if (!Environment.HasShutdownStarted && +// !AppDomain.CurrentDomain.IsFinalizingForUnload()) +// { +// Console.WriteLine("GcNotify called"); +// gcEvent.Set(); +// new GcNotify(); +// } +// } +// } + + /// + /// Test adding an object to a scene. + /// + [Fact] + public void TestAddSceneObject() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + int partsToTestCount = 3; + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(partsToTestCount, TestHelpers.ParseTail(0x1), "obj1", 0x10); + SceneObjectPart[] parts = so.Parts; + + scene.AddNewSceneObject(so, false).Should().BeTrue(); + //Assert.That(scene.AddNewSceneObject(so, false), Is.True); + + SceneObjectGroup retrievedSo = scene.GetSceneObjectGroup(so.UUID); + SceneObjectPart[] retrievedParts = retrievedSo.Parts; + + //m_log.Debug("retrievedPart : {0}", retrievedPart); + // If the parts have the same UUID then we will consider them as one and the same + retrievedSo.PrimCount.Should().Be(partsToTestCount); + // Assert.That(retrievedSo.PrimCount, Is.EqualTo(partsToTestCount)); + + for (int i = 0; i < partsToTestCount; i++) + { + retrievedParts[i].Name.Should().Be(parts[i].Name); + retrievedParts[i].UUID.Should().Be(parts[i].UUID); + //Assert.That(retrievedParts[i].Name, Is.EqualTo(parts[i].Name)); + //Assert.That(retrievedParts[i].UUID, Is.EqualTo(parts[i].UUID)); + } + } + + [Fact] + /// + /// It shouldn't be possible to add a scene object if one with that uuid already exists in the scene. + /// + public void TestAddExistingSceneObjectUuid() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + + string obj1Name = "Alfred"; + string obj2Name = "Betty"; + UUID objUuid = new UUID("00000000-0000-0000-0000-000000000001"); + + SceneObjectPart part1 + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = obj1Name, UUID = objUuid }; + + Assert.That(scene.AddNewSceneObject(new SceneObjectGroup(part1), false), Is.True); + + SceneObjectPart part2 + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = obj2Name, UUID = objUuid }; + + Assert.That(scene.AddNewSceneObject(new SceneObjectGroup(part2), false), Is.False); + + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(objUuid); + + //m_log.Debug("retrievedPart : {0}", retrievedPart); + // If the parts have the same UUID then we will consider them as one and the same + Assert.That(retrievedPart.Name, Is.EqualTo(obj1Name)); + Assert.That(retrievedPart.UUID, Is.EqualTo(objUuid)); + } + + /// + /// Test retrieving a scene object via the local id of one of its parts. + /// + [Fact] + public void TestGetSceneObjectByPartLocalId() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + int partsToTestCount = 3; + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(partsToTestCount, TestHelpers.ParseTail(0x1), "obj1", 0x10); + SceneObjectPart[] parts = so.Parts; + + scene.AddNewSceneObject(so, false); + + // Test getting via the root part's local id + Assert.That(scene.GetGroupByPrim(so.LocalId), Is.Not.Null); + + // Test getting via a non root part's local id + Assert.That(scene.GetGroupByPrim(parts[partsToTestCount - 1].LocalId), Is.Not.Null); + + // Test that we don't get back an object for a local id that doesn't exist + Assert.That(scene.GetGroupByPrim(999), Is.Null); + + uint soid = so.LocalId; + uint spid = parts[partsToTestCount - 1].LocalId; + + // Now delete the scene object and check again + scene.DeleteSceneObject(so, false); + + Assert.That(scene.GetGroupByPrim(soid), Is.Null); + Assert.That(scene.GetGroupByPrim(spid), Is.Null); + } + + /// + /// Test deleting an object from a scene. + /// + /// + /// This is the most basic form of delete. For all more sophisticated forms of derez (done asynchrnously + /// and where object can be taken to user inventory, etc.), see SceneObjectDeRezTests. + /// + [Fact] + public void TestDeleteSceneObject() + { + TestHelpers.InMethod(); + + TestScene scene = new SceneHelpers().SetupScene(); + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene); + + Assert.That(so.IsDeleted, Is.False); + uint retrievedPartID = so.LocalId; + + scene.DeleteSceneObject(so, false); + + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(retrievedPartID); + + Assert.That(retrievedPart, Is.Null); + } + + /// + /// Changing a scene object uuid changes the root part uuid. This is a valid operation if the object is not + /// in a scene and is useful if one wants to supply a UUID directly rather than use the one generated by + /// OpenSim. + /// + [Fact] + public void TestChangeSceneObjectUuid() + { + string rootPartName = "rootpart"; + UUID rootPartUuid = new UUID("00000000-0000-0000-0000-000000000001"); + string childPartName = "childPart"; + UUID childPartUuid = new UUID("00000000-0000-0000-0001-000000000000"); + + SceneObjectPart rootPart + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = rootPartName, UUID = rootPartUuid }; + SceneObjectPart linkPart + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = childPartName, UUID = childPartUuid }; + + SceneObjectGroup sog = new SceneObjectGroup(rootPart); + sog.AddPart(linkPart); + + Assert.That(sog.UUID, Is.EqualTo(rootPartUuid)); + Assert.That(sog.RootPart.UUID, Is.EqualTo(rootPartUuid)); + Assert.That(sog.Parts.Length, Is.EqualTo(2)); + + UUID newRootPartUuid = new UUID("00000000-0000-0000-0000-000000000002"); + sog.UUID = newRootPartUuid; + + Assert.That(sog.UUID, Is.EqualTo(newRootPartUuid)); + Assert.That(sog.RootPart.UUID, Is.EqualTo(newRootPartUuid)); + Assert.That(sog.Parts.Length, Is.EqualTo(2)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCopyTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCopyTests.cs new file mode 100644 index 00000000000..06d43c41a31 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCopyTests.cs @@ -0,0 +1,346 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /* + /// + /// Test copying of scene objects. + /// + /// + /// This is at a level above the SceneObjectBasicTests, which act on the scene directly. + /// + [TestFixture] + public class SceneObjectCopyTests : OpenSimTestCase + { + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + // This facility was added after the original async delete tests were written, so it may be possible now + // to not bother explicitly disabling their async (since everything will be running sync). + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [Test] + public void TestTakeCopyWhenCopierIsOwnerWithPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", ua.PrincipalID); + uint soLocalId = so.LocalId; +// so.UpdatePermissions( +// ua.PrincipalID, (byte)PermissionWho.Owner, so.LocalId, (uint)OpenMetaverse.PermissionMask.Copy, 1); +// so.UpdatePermissions( +// ua.PrincipalID, (byte)PermissionWho.Owner, so.LocalId, (uint)OpenMetaverse.PermissionMask.Transfer, 0); +// so.UpdatePermissions( +// ua.PrincipalID, (byte)PermissionWho.Base, so.LocalId, (uint)OpenMetaverse.PermissionMask.Transfer, 0); +// scene.HandleObjectPermissionsUpdate(client, client.AgentId, client.SessionId, (byte)PermissionWho.Owner, so.LocalId, (uint)OpenMetaverse.PermissionMask.Transfer, 0); + + // Ideally we might change these via client-focussed method calls as commented out above. However, this + // becomes very convoluted so we will set only the copy perm directly. + so.RootPart.BaseMask = (uint)OpenMetaverse.PermissionMask.Copy; +// so.RootPart.OwnerMask = (uint)OpenMetaverse.PermissionMask.Copy; + + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will currently plop it in Lost and Found + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Lost And Found/so1"); + Assert.That(item, Is.Not.Null); + } + + [Test] + public void TestTakeCopyWhenCopierIsOwnerWithoutPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", ua.PrincipalID); + uint soLocalId = so.LocalId; + + so.RootPart.BaseMask = (uint)(OpenMetaverse.PermissionMask.All & ~OpenMetaverse.PermissionMask.Copy); + //so.RootPart.OwnerMask = (uint)(OpenMetaverse.PermissionMask.Copy & ~OpenMetaverse.PermissionMask.Copy); + + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will currently plop it in Lost and Found + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we do not have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Lost And Found/so1"); + Assert.That(item, Is.Null); + } + + [Test] + public void TestTakeCopyWhenCopierIsNotOwnerWithPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", TestHelpers.ParseTail(0x2)); + uint soLocalId = so.LocalId; + + // Base must allow transfer and copy + so.RootPart.BaseMask = (uint)(OpenMetaverse.PermissionMask.Copy | OpenMetaverse.PermissionMask.Transfer); + // Must be set so anyone can copy + so.RootPart.EveryoneMask = (uint)OpenMetaverse.PermissionMask.Copy; + + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Not.Null); + } + + [Test] + public void TestTakeCopyWhenCopierIsNotOwnerWithoutPerms() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + TestScene scene = new SceneHelpers().SetupScene("s1", TestHelpers.ParseTail(0x99), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, new PermissionsModule(), new BasicInventoryAccessModule()); + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(0x1)); + TestClient client = (TestClient)SceneHelpers.AddScenePresence(scene, ua.PrincipalID).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", TestHelpers.ParseTail(0x2)); + uint soLocalId = so.LocalId; + + { + // Check that object is not copied if copy base perms is missing. + // Should not allow copy if base does not have this. + so.RootPart.BaseMask = (uint)OpenMetaverse.PermissionMask.Transfer; + // Must be set so anyone can copy + so.RootPart.EveryoneMask = (uint)OpenMetaverse.PermissionMask.Copy; + + // Check that object is not copied + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder if we have perms + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Null); + } + + { + // Check that object is not copied if copy trans perms is missing. + // Should not allow copy if base does not have this. + so.RootPart.BaseMask = (uint)OpenMetaverse.PermissionMask.Copy; + // Must be set so anyone can copy + so.RootPart.EveryoneMask = (uint)OpenMetaverse.PermissionMask.Copy; + + // Check that object is not copied + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder if we have perms + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Null); + } + + { + // Check that object is not copied if everyone copy perms is missing. + // Should not allow copy if base does not have this. + so.RootPart.BaseMask = (uint)(OpenMetaverse.PermissionMask.Copy | OpenMetaverse.PermissionMask.Transfer); + // Make sure everyone perm does not allow copy + so.RootPart.EveryoneMask = (uint)(OpenMetaverse.PermissionMask.All & ~OpenMetaverse.PermissionMask.Copy); + + // Check that object is not copied + List localIds = new List(); + localIds.Add(so.LocalId); + + // Specifying a UUID.Zero in this case will plop it in the Objects folder if we have perms + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.TakeCopy, UUID.Zero); + + // Check that object isn't copied until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Not.Null); + Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + // Check that object is still there. + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Not.Null); + Assert.That(client.ReceivedKills.Count, Is.EqualTo(0)); + + // Check that we have a copy in inventory + InventoryItemBase item + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, ua.PrincipalID, "Objects/so1"); + Assert.That(item, Is.Null); + } + } + } + */ +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCrossingTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCrossingTests.cs new file mode 100644 index 00000000000..accd0043ad5 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectCrossingTests.cs @@ -0,0 +1,294 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using Nini.Config; +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Land; +using OpenSim.Region.OptionalModules; +using OpenSim.Tests.Common; +using Xunit.Sdk; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + public class SceneObjectCrossingTests : OpenSimTestCase + { + protected SceneObjectCrossingTests() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + public override void Dispose() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + base.Dispose(); + } + + /// + /// Test cross with no prim limit module. + /// + [Fact] + public void TestCrossOnSameSimulator() + { + + TestHelpers.InMethod(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, etmA); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + + SceneObjectGroup so1 = SceneHelpers.AddSceneObject(sceneA, 1, userId, "", sceneObjectIdTail); + UUID so1Id = so1.UUID; + so1.AbsolutePosition = new Vector3(128, 10, 20); + + // Cross with a negative value + so1.AbsolutePosition = new Vector3(128, -10, 20); + + // crossing is async + Thread.Sleep(500); + + sceneA.GetSceneObjectGroup(so1Id).Should().BeNull(); + sceneB.GetSceneObjectGroup(so1Id).Should().NotBeNull(); + //Assert.IsNull(sceneA.GetSceneObjectGroup(so1Id)); + //Assert.NotNull(sceneB.GetSceneObjectGroup(so1Id)); + } + + /// + /// Test cross with no prim limit module. + /// + /// + /// Possibly this should belong in ScenePresenceCrossingTests, though here it is the object that is being moved + /// where the avatar is just a passenger. + /// + [Fact] + public void TestCrossOnSameSimulatorWithSittingAvatar() + { + TestHelpers.InMethod(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + Vector3 so1StartPos = new Vector3(128, 10, 20); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + SceneObjectGroup so1 = SceneHelpers.AddSceneObject(sceneA, 1, userId, "", sceneObjectIdTail); + UUID so1Id = so1.UUID; + so1.AbsolutePosition = so1StartPos; + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence sp1SceneA = SceneHelpers.AddScenePresence(sceneA, tc, acd); + sp1SceneA.AbsolutePosition = so1StartPos; + sp1SceneA.HandleAgentRequestSit(sp1SceneA.ControllingClient, sp1SceneA.UUID, so1.UUID, Vector3.Zero); + + sceneA.Update(4); + sceneB.Update(4); + // Cross + sceneA.SceneGraph.UpdatePrimGroupPosition( + so1.LocalId, new Vector3(so1StartPos.X, so1StartPos.Y - 20, so1StartPos.Z), sp1SceneA.ControllingClient); + + // crossing is async + sceneA.Update(4); + sceneB.Update(4); + Thread.Sleep(500); + + SceneObjectGroup so1PostCross; + + ScenePresence sp1SceneAPostCross = sceneA.GetScenePresence(userId); + sp1SceneAPostCross.IsChildAgent.Should().BeTrue("sp1SceneAPostCross.IsChildAgent unexpectedly false"); + //Assert.IsTrue(sp1SceneAPostCross.IsChildAgent, "sp1SceneAPostCross.IsChildAgent unexpectedly false"); + + ScenePresence sp1SceneBPostCross = sceneB.GetScenePresence(userId); + TestClient sceneBTc = ((TestClient)sp1SceneBPostCross.ControllingClient); + sceneBTc.CompleteMovement(); + + sceneA.Update(4); + sceneB.Update(4); + + sp1SceneBPostCross.IsChildAgent.Should().BeFalse("sp1SceneAPostCross.IsChildAgent unexpectedly true"); + //Assert.IsFalse(sp1SceneBPostCross.IsChildAgent, "sp1SceneAPostCross.IsChildAgent unexpectedly true"); + + sp1SceneBPostCross.IsSatOnObject.Should().BeTrue(); + //Assert.IsTrue(sp1SceneBPostCross.IsSatOnObject); + + sceneA.GetSceneObjectGroup(so1Id).Should().BeNull("uck"); + //Assert.IsNull(sceneA.GetSceneObjectGroup(so1Id), "uck"); + + so1PostCross = sceneB.GetSceneObjectGroup(so1Id); + so1PostCross.Should().NotBeNull(); + so1PostCross.GetSittingAvatarsCount().Should().Be(1); + //Assert.NotNull(so1PostCross); + //Assert.AreEqual(1, so1PostCross.GetSittingAvatarsCount()); + + Vector3 so1PostCrossPos = so1PostCross.AbsolutePosition; + + // Recross + sceneB.SceneGraph.UpdatePrimGroupPosition( + so1PostCross.LocalId, new Vector3(so1PostCrossPos.X, so1PostCrossPos.Y + 20, so1PostCrossPos.Z), sp1SceneBPostCross.ControllingClient); + + sceneA.Update(4); + sceneB.Update(4); + + // crossing is async + Thread.Sleep(500); + + { + ScenePresence sp1SceneBPostReCross = sceneB.GetScenePresence(userId); + sp1SceneBPostReCross.IsChildAgent.Should().BeTrue("sp1SceneBPostReCross.IsChildAgent unexpectedly false"); + //Assert.IsTrue(sp1SceneBPostReCross.IsChildAgent, "sp1SceneBPostReCross.IsChildAgent unexpectedly false"); + + ScenePresence sp1SceneAPostReCross = sceneA.GetScenePresence(userId); + TestClient sceneATc = ((TestClient)sp1SceneAPostReCross.ControllingClient); + sceneATc.CompleteMovement(); + + sp1SceneAPostReCross.IsChildAgent.Should().BeTrue("sp1SceneAPostCross.IsChildAgent unexpectedly true"); + sp1SceneAPostReCross.IsSatOnObject.Should().BeTrue(""); + //Assert.IsFalse(sp1SceneAPostReCross.IsChildAgent, "sp1SceneAPostCross.IsChildAgent unexpectedly true"); + //Assert.IsTrue(sp1SceneAPostReCross.IsSatOnObject); + + sceneB.GetSceneObjectGroup(so1Id).Should().BeNull("uck2"); + SceneObjectGroup so1PostReCross = sceneA.GetSceneObjectGroup(so1Id); + so1PostReCross.Should().NotBeNull(""); + so1PostReCross.GetSittingAvatarsCount().Should().Be(1); + + //Assert.IsNull(sceneB.GetSceneObjectGroup(so1Id), "uck2"); + //SceneObjectGroup so1PostReCross = sceneA.GetSceneObjectGroup(so1Id); + //Assert.NotNull(so1PostReCross); + //Assert.AreEqual(1, so1PostReCross.GetSittingAvatarsCount()); + } + } + + /// + /// Test cross with no prim limit module.Test PrimLimitsModuleTest class in optional module tests in the + /// future (though it is configured as active by default, so not really optional). + /// + [Fact] + public void TestCrossOnSameSimulatorPrimLimitsOkay() + { + TestHelpers.InMethod(); + + UUID userId = TestHelpers.ParseTail(0x1); + int sceneObjectIdTail = 0x2; + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + LandManagementModule lmmA = new LandManagementModule(); + LandManagementModule lmmB = new LandManagementModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + IConfig permissionsConfig = config.AddConfig("Permissions"); + permissionsConfig.Set("permissionmodules", "PrimLimitsModule"); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, etmA, lmmA, new PrimLimitsModule(), new PrimCountModule()); + SceneHelpers.SetupSceneModules(sceneB, config, etmB, lmmB, new PrimLimitsModule(), new PrimCountModule()); + + // We must set up the parcel for this to work. Normally this is taken care of by OpenSimulator startup + // code which is not yet easily invoked by tests. + lmmA.EventManagerOnNoLandDataFromStorage(); + lmmB.EventManagerOnNoLandDataFromStorage(); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + ScenePresence sp1SceneA = SceneHelpers.AddScenePresence(sceneA, tc, acd); + + SceneObjectGroup so1 = SceneHelpers.AddSceneObject(sceneA, 1, userId, "", sceneObjectIdTail); + UUID so1Id = so1.UUID; + so1.AbsolutePosition = new Vector3(128, 10, 20); + + // Cross with a negative value. We must make this call rather than setting AbsolutePosition directly + // because only this will execute permission checks in the source region. + sceneA.SceneGraph.UpdatePrimGroupPosition(so1.LocalId, new Vector3(128, -10, 20), sp1SceneA.ControllingClient); + + // crossing is async + Thread.Sleep(500); + + sceneA.GetSceneObjectGroup(so1Id).Should().BeNull(); + sceneB.GetSceneObjectGroup(so1Id).Should().NotBeNull(); + //Assert.IsNull(sceneA.GetSceneObjectGroup(so1Id)); + //Assert.NotNull(sceneB.GetSceneObjectGroup(so1Id)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectDeRezTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectDeRezTests.cs new file mode 100644 index 00000000000..04e348cc785 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectDeRezTests.cs @@ -0,0 +1,253 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Tests derez of scene objects. + /// + /// + /// This is at a level above the SceneObjectBasicTests, which act on the scene directly. + /// TODO: These tests are incomplete - need to test more kinds of derez (e.g. return object). + /// + [TestFixture] + public class SceneObjectDeRezTests : OpenSimTestCase + { + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + // This facility was added after the original async delete tests were written, so it may be possible now + // to not bother explicitly disabling their async (since everything will be running sync). + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + /// + /// Test deleting an object from a scene. + /// + [Test] + public void TestDeRezSceneObject() + { + TestHelpers.InMethod(); + + UUID userId = UUID.Parse("10000000-0000-0000-0000-000000000001"); + + TestScene scene = new SceneHelpers().SetupScene(); + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Startup"); + config.Set("serverside_object_permissions", true); + SceneHelpers.SetupSceneModules(scene, configSource, new object[] { new DefaultPermissionsModule() }); + IClientAPI client = SceneHelpers.AddScenePresence(scene, userId).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, "so1", userId); + uint soLocalId = so.LocalId; + + List localIds = new List(); + localIds.Add(so.LocalId); + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.Delete, UUID.Zero); + + // Check that object isn't deleted until we crank the sogd handle. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); +// Assert.That(retrievedPart, Is.Not.Null); +// Assert.That(retrievedPart.ParentGroup.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + +// SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart, Is.Null); + } + + /// + /// Test that child and root agents correctly receive KillObject notifications. + /// + [Test] + public void TestDeRezSceneObjectToAgents() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + // We need this so that the creation of the root client for userB in sceneB can trigger the creation of a child client in sceneA + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + EntityTransferModule etmB = new EntityTransferModule(); + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmB.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + + // We need this for derez + //SceneHelpers.SetupSceneModules(sceneA, new PermissionsModule()); + + UserAccount uaA = UserAccountHelpers.CreateUserWithInventory(sceneA, "Andy", "AAA", 0x1, ""); + UserAccount uaB = UserAccountHelpers.CreateUserWithInventory(sceneA, "Brian", "BBB", 0x2, ""); + + TestClient clientA = (TestClient)SceneHelpers.AddScenePresence(sceneA, uaA).ControllingClient; + + // This is the more long-winded route we have to take to get a child client created for userB in sceneA + // rather than just calling AddScenePresence() as for userA + AgentCircuitData acd = SceneHelpers.GenerateAgentData(uaB); + TestClient clientB = new TestClient(acd, sceneB); + List childClientsB = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(clientB, childClientsB); + + SceneHelpers.AddScenePresence(sceneB, clientB, acd); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(sceneA); + uint soLocalId = so.LocalId; + + sceneA.DeleteSceneObject(so, false); + } + + /// + /// Test deleting an object from a scene where the deleter is not the owner + /// + /// + /// This test assumes that the deleter is not a god. + /// + [Test] + public void TestDeRezSceneObjectNotOwner() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = UUID.Parse("10000000-0000-0000-0000-000000000001"); + UUID objectOwnerId = UUID.Parse("20000000-0000-0000-0000-000000000001"); + + TestScene scene = new SceneHelpers().SetupScene(); + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Startup"); + config.Set("serverside_object_permissions", true); + SceneHelpers.SetupSceneModules(scene, configSource, new object[] { new DefaultPermissionsModule() }); + IClientAPI client = SceneHelpers.AddScenePresence(scene, userId).ControllingClient; + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectPart part + = new SceneObjectPart(objectOwnerId, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero); + part.Name = "obj1"; + scene.AddNewSceneObject(new SceneObjectGroup(part), false); + List localIds = new List(); + localIds.Add(part.LocalId); + + scene.DeRezObjects(client, localIds, UUID.Zero, DeRezAction.Delete, UUID.Zero); + sogd.InventoryDeQueueAndDelete(); + + // Object should still be in the scene. + SceneObjectPart retrievedPart = scene.GetSceneObjectPart(part.LocalId); + Assert.That(retrievedPart.UUID, Is.EqualTo(part.UUID)); + } + + /// + /// Test deleting an object asynchronously to user inventory. + /// + [Test] + public void TestDeleteSceneObjectAsyncToUserInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID agentId = UUID.Parse("00000000-0000-0000-0000-000000000001"); + string myObjectName = "Fred"; + + TestScene scene = new SceneHelpers().SetupScene(); + + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Modules"); + config.Set("InventoryAccessModule", "BasicInventoryAccessModule"); + SceneHelpers.SetupSceneModules( + scene, configSource, new object[] { new BasicInventoryAccessModule() }); + + SceneHelpers.SetupSceneModules(scene, new object[] { }); + + // Turn off the timer on the async sog deleter - we'll crank it by hand for this test. + AsyncSceneObjectGroupDeleter sogd = scene.SceneObjectGroupDeleter; + sogd.Enabled = false; + + SceneObjectGroup so = SceneHelpers.AddSceneObject(scene, myObjectName, agentId); + + UserAccount ua = UserAccountHelpers.CreateUserWithInventory(scene, agentId); + InventoryFolderBase folder1 + = UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, ua.PrincipalID, "folder1", false); + + IClientAPI client = SceneHelpers.AddScenePresence(scene, agentId).ControllingClient; + scene.DeRezObjects(client, new List() { so.LocalId }, UUID.Zero, DeRezAction.Take, folder1.ID); + +// SceneObjectPart retrievedPart = scene.GetSceneObjectPart(so.LocalId); + +// Assert.That(retrievedPart, Is.Not.Null); +// Assert.That(so.IsDeleted, Is.False); + + sogd.InventoryDeQueueAndDelete(); + + Assert.That(so.IsDeleted, Is.True); + + SceneObjectPart retrievedPart2 = scene.GetSceneObjectPart(so.LocalId); + Assert.That(retrievedPart2, Is.Null); + +// SceneSetupHelpers.DeleteSceneObjectAsync(scene, part, DeRezAction.Take, userInfo.RootFolder.ID, client); + + InventoryItemBase retrievedItem + = UserInventoryHelpers.GetInventoryItem( + scene.InventoryService, ua.PrincipalID, "folder1/" + myObjectName); + + // Check that we now have the taken part in our inventory + Assert.That(retrievedItem, Is.Not.Null); + + // Check that the taken part has actually disappeared +// SceneObjectPart retrievedPart = scene.GetSceneObjectPart(part.LocalId); +// Assert.That(retrievedPart, Is.Null); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectLinkingTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectLinkingTests.cs new file mode 100644 index 00000000000..4d97c07b9ea --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectLinkingTests.cs @@ -0,0 +1,368 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using Microsoft.Extensions.Logging; + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class SceneObjectLinkingTests : OpenSimTestCase + { + private static readonly ILogger m_logger = LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + + /// + /// Links to self should be ignored. + /// + [Test] + public void TestLinkToSelf() + { + TestHelpers.InMethod(); + + UUID ownerId = TestHelpers.ParseTail(0x1); + int nParts = 3; + + TestScene scene = new SceneHelpers().SetupScene(); + SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(nParts, ownerId, "TestLinkToSelf_", 0x10); + scene.AddSceneObject(sog1); + scene.LinkObjects(ownerId, sog1.LocalId, new List() { sog1.Parts[1].LocalId }); +// sog1.LinkToGroup(sog1); + + Assert.That(sog1.Parts.Length, Is.EqualTo(nParts)); + } + + [Test] + public void TestLinkDelink2SceneObjects() + { + TestHelpers.InMethod(); + + bool debugtest = false; + + Scene scene = new SceneHelpers().SetupScene(); + SceneObjectGroup grp1 = SceneHelpers.AddSceneObject(scene); + SceneObjectPart part1 = grp1.RootPart; + SceneObjectGroup grp2 = SceneHelpers.AddSceneObject(scene); + SceneObjectPart part2 = grp2.RootPart; + + grp1.AbsolutePosition = new Vector3(10, 10, 10); + grp2.AbsolutePosition = Vector3.Zero; + + // <90,0,0> +// grp1.UpdateGroupRotationR(Quaternion.CreateFromEulers(90 * Utils.DEG_TO_RAD, 0, 0)); + + // <180,0,0> + grp2.UpdateGroupRotationR(Quaternion.CreateFromEulers(180 * Utils.DEG_TO_RAD, 0, 0)); + + // Required for linking + grp1.RootPart.ClearUpdateSchedule(); + grp2.RootPart.ClearUpdateSchedule(); + + // Link grp2 to grp1. part2 becomes child prim to grp1. grp2 is eliminated. + Assert.IsFalse(grp1.GroupContainsForeignPrims); + grp1.LinkToGroup(grp2); + Assert.IsTrue(grp1.GroupContainsForeignPrims); + + scene.Backup(true); + Assert.IsFalse(grp1.GroupContainsForeignPrims); + + // FIXME: Can't do this test yet since group 2 still has its root part! We can't yet null this since + // it might cause SOG.ProcessBackup() to fail due to the race condition. This really needs to be fixed. + Assert.That(grp2.IsDeleted, "SOG 2 was not registered as deleted after link."); + Assert.That(grp2.Parts.Length, Is.EqualTo(0), "Group 2 still contained children after delink."); + Assert.That(grp1.Parts.Length == 2); + + if (debugtest) + { + m_logger?.LogDebug("parts: " + grp1.Parts.Length); + m_logger?.LogDebug("Group1: Pos:"+grp1.AbsolutePosition+", Rot:"+grp1.GroupRotation); + m_logger?.LogDebug("Group1: Prim1: OffsetPosition:"+ part1.OffsetPosition+", OffsetRotation:"+part1.RotationOffset); + m_logger?.LogDebug("Group1: Prim2: OffsetPosition:"+part2.OffsetPosition+", OffsetRotation:"+part2.RotationOffset); + } + + // root part should have no offset position or rotation + Assert.That(part1.OffsetPosition == Vector3.Zero && part1.RotationOffset == Quaternion.Identity, + "root part should have no offset position or rotation"); + + // offset position should be root part position - part2.absolute position. + Assert.That(part2.OffsetPosition == new Vector3(-10, -10, -10), + "offset position should be root part position - part2.absolute position."); + + float roll = 0; + float pitch = 0; + float yaw = 0; + + // There's a euler anomoly at 180, 0, 0 so expect 180 to turn into -180. + part1.RotationOffset.GetEulerAngles(out roll, out pitch, out yaw); + Vector3 rotEuler1 = new Vector3(roll * Utils.RAD_TO_DEG, pitch * Utils.RAD_TO_DEG, yaw * Utils.RAD_TO_DEG); + + if (debugtest) + m_logger?.LogDebug(rotEuler1); + + part2.RotationOffset.GetEulerAngles(out roll, out pitch, out yaw); + Vector3 rotEuler2 = new Vector3(roll * Utils.RAD_TO_DEG, pitch * Utils.RAD_TO_DEG, yaw * Utils.RAD_TO_DEG); + + if (debugtest) + m_logger?.LogDebug(rotEuler2); + + Assert.That(rotEuler2.ApproxEquals(new Vector3(-180, 0, 0), 0.001f) || rotEuler2.ApproxEquals(new Vector3(180, 0, 0), 0.001f), + "Not exactly sure what this is asserting..."); + + // Delink part 2 + SceneObjectGroup grp3 = grp1.DelinkFromGroup(part2.LocalId); + + if (debugtest) + m_logger?.LogDebug("Group2: Prim2: OffsetPosition:" + part2.AbsolutePosition + ", OffsetRotation:" + part2.RotationOffset); + + Assert.That(grp1.Parts.Length, Is.EqualTo(1), "Group 1 still contained part2 after delink."); + Assert.That(part2.AbsolutePosition == Vector3.Zero, "The absolute position should be zero"); + Assert.NotNull(grp3); + } + + [Test] + public void TestLinkDelink2groups4SceneObjects() + { + TestHelpers.InMethod(); + + bool debugtest = false; + + Scene scene = new SceneHelpers().SetupScene(); + SceneObjectGroup grp1 = SceneHelpers.AddSceneObject(scene); + SceneObjectPart part1 = grp1.RootPart; + SceneObjectGroup grp2 = SceneHelpers.AddSceneObject(scene); + SceneObjectPart part2 = grp2.RootPart; + SceneObjectGroup grp3 = SceneHelpers.AddSceneObject(scene); + SceneObjectPart part3 = grp3.RootPart; + SceneObjectGroup grp4 = SceneHelpers.AddSceneObject(scene); + SceneObjectPart part4 = grp4.RootPart; + + grp1.AbsolutePosition = new Vector3(10, 10, 10); + grp2.AbsolutePosition = Vector3.Zero; + grp3.AbsolutePosition = new Vector3(20, 20, 20); + grp4.AbsolutePosition = new Vector3(40, 40, 40); + + // <90,0,0> +// grp1.UpdateGroupRotationR(Quaternion.CreateFromEulers(90 * Utils.DEG_TO_RAD, 0, 0)); + + // <180,0,0> + grp2.UpdateGroupRotationR(Quaternion.CreateFromEulers(180 * Utils.DEG_TO_RAD, 0, 0)); + + // <270,0,0> +// grp3.UpdateGroupRotationR(Quaternion.CreateFromEulers(270 * Utils.DEG_TO_RAD, 0, 0)); + + // <0,90,0> + grp4.UpdateGroupRotationR(Quaternion.CreateFromEulers(0, 90 * Utils.DEG_TO_RAD, 0)); + + // Required for linking + grp1.RootPart.ClearUpdateSchedule(); + grp2.RootPart.ClearUpdateSchedule(); + grp3.RootPart.ClearUpdateSchedule(); + grp4.RootPart.ClearUpdateSchedule(); + + // Link grp2 to grp1. part2 becomes child prim to grp1. grp2 is eliminated. + grp1.LinkToGroup(grp2); + + // Link grp4 to grp3. + grp3.LinkToGroup(grp4); + + // At this point we should have 4 parts total in two groups. + Assert.That(grp1.Parts.Length == 2, "Group1 children count should be 2"); + Assert.That(grp2.IsDeleted, "Group 2 was not registered as deleted after link."); + Assert.That(grp2.Parts.Length, Is.EqualTo(0), "Group 2 still contained parts after delink."); + Assert.That(grp3.Parts.Length == 2, "Group3 children count should be 2"); + Assert.That(grp4.IsDeleted, "Group 4 was not registered as deleted after link."); + Assert.That(grp4.Parts.Length, Is.EqualTo(0), "Group 4 still contained parts after delink."); + + if (debugtest) + { + m_logger?.LogDebug("--------After Link-------"); + m_logger?.LogDebug("Group1: parts:" + grp1.Parts.Length); + m_logger?.LogDebug("Group1: Pos:"+grp1.AbsolutePosition+", Rot:"+grp1.GroupRotation); + m_logger?.LogDebug("Group1: Prim1: OffsetPosition:" + part1.OffsetPosition + ", OffsetRotation:" + part1.RotationOffset); + m_logger?.LogDebug("Group1: Prim2: OffsetPosition:"+part2.OffsetPosition+", OffsetRotation:"+ part2.RotationOffset); + + m_logger?.LogDebug("Group3: parts:" + grp3.Parts.Length); + m_logger?.LogDebug("Group3: Pos:"+grp3.AbsolutePosition+", Rot:"+grp3.GroupRotation); + m_logger?.LogDebug("Group3: Prim1: OffsetPosition:"+part3.OffsetPosition+", OffsetRotation:"+part3.RotationOffset); + m_logger?.LogDebug("Group3: Prim2: OffsetPosition:"+part4.OffsetPosition+", OffsetRotation:"+part4.RotationOffset); + } + + // Required for linking + grp1.RootPart.ClearUpdateSchedule(); + grp3.RootPart.ClearUpdateSchedule(); + + // root part should have no offset position or rotation + Assert.That(part1.OffsetPosition == Vector3.Zero && part1.RotationOffset == Quaternion.Identity, + "root part should have no offset position or rotation (again)"); + + // offset position should be root part position - part2.absolute position. + Assert.That(part2.OffsetPosition == new Vector3(-10, -10, -10), + "offset position should be root part position - part2.absolute position (again)"); + + float roll = 0; + float pitch = 0; + float yaw = 0; + + // There's a euler anomoly at 180, 0, 0 so expect 180 to turn into -180. + part1.RotationOffset.GetEulerAngles(out roll, out pitch, out yaw); + Vector3 rotEuler1 = new Vector3(roll * Utils.RAD_TO_DEG, pitch * Utils.RAD_TO_DEG, yaw * Utils.RAD_TO_DEG); + + if (debugtest) + m_logger?.LogDebug(rotEuler1); + + part2.RotationOffset.GetEulerAngles(out roll, out pitch, out yaw); + Vector3 rotEuler2 = new Vector3(roll * Utils.RAD_TO_DEG, pitch * Utils.RAD_TO_DEG, yaw * Utils.RAD_TO_DEG); + + if (debugtest) + m_logger?.LogDebug(rotEuler2); + + Assert.That(rotEuler2.ApproxEquals(new Vector3(-180, 0, 0), 0.001f) || rotEuler2.ApproxEquals(new Vector3(180, 0, 0), 0.001f), + "Not sure what this assertion is all about..."); + + // Now we're linking the first group to the third group. This will make the first group child parts of the third one. + grp3.LinkToGroup(grp1); + + // Delink parts 2 and 3 + grp3.DelinkFromGroup(part2.LocalId); + grp3.DelinkFromGroup(part3.LocalId); + + if (debugtest) + { + m_logger?.LogDebug("--------After De-Link-------"); + m_logger?.LogDebug("Group1: parts:" + grp1.Parts.Length); + m_logger?.LogDebug("Group1: Pos:" + grp1.AbsolutePosition + ", Rot:" + grp1.GroupRotation); + m_logger?.LogDebug("Group1: Prim1: OffsetPosition:" + part1.OffsetPosition + ", OffsetRotation:" + part1.RotationOffset); + m_logger?.LogDebug("Group1: Prim2: OffsetPosition:" + part2.OffsetPosition + ", OffsetRotation:" + part2.RotationOffset); + + m_logger?.LogDebug("Group3: parts:" + grp3.Parts.Length); + m_logger?.LogDebug("Group3: Pos:" + grp3.AbsolutePosition + ", Rot:" + grp3.GroupRotation); + m_logger?.LogDebug("Group3: Prim1: OffsetPosition:" + part3.OffsetPosition + ", OffsetRotation:" + part3.RotationOffset); + m_logger?.LogDebug("Group3: Prim2: OffsetPosition:" + part4.OffsetPosition + ", OffsetRotation:" + part4.RotationOffset); + } + + Assert.That(part2.AbsolutePosition == Vector3.Zero, "Badness 1"); + Assert.That(part4.OffsetPosition == new Vector3(20, 20, 20), "Badness 2"); + Quaternion compareQuaternion = new Quaternion(0, 0.7071068f, 0, 0.7071068f); + Assert.That((part4.RotationOffset.X - compareQuaternion.X < 0.00003) + && (part4.RotationOffset.Y - compareQuaternion.Y < 0.00003) + && (part4.RotationOffset.Z - compareQuaternion.Z < 0.00003) + && (part4.RotationOffset.W - compareQuaternion.W < 0.00003), + "Badness 3"); + } + + /// + /// Test that a new scene object which is already linked is correctly persisted to the persistence layer. + /// + [Test] + public void TestNewSceneObjectLinkPersistence() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + + string rootPartName = "rootpart"; + UUID rootPartUuid = new UUID("00000000-0000-0000-0000-000000000001"); + string linkPartName = "linkpart"; + UUID linkPartUuid = new UUID("00000000-0000-0000-0001-000000000000"); + + SceneObjectPart rootPart + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = rootPartName, UUID = rootPartUuid }; + SceneObjectPart linkPart + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = linkPartName, UUID = linkPartUuid }; + + SceneObjectGroup sog = new SceneObjectGroup(rootPart); + sog.AddPart(linkPart); + scene.AddNewSceneObject(sog, true); + + // In a test, we have to crank the backup handle manually. Normally this would be done by the timer invoked + // scene backup thread. + scene.Backup(true); + + List storedObjects = scene.SimulationDataService.LoadObjects(scene.RegionInfo.RegionID); + + Assert.That(storedObjects.Count, Is.EqualTo(1)); + Assert.That(storedObjects[0].Parts.Length, Is.EqualTo(2)); + Assert.That(storedObjects[0].ContainsPart(rootPartUuid)); + Assert.That(storedObjects[0].ContainsPart(linkPartUuid)); + } + + /// + /// Test that a delink of a previously linked object is correctly persisted to the database + /// + [Test] + public void TestDelinkPersistence() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + + string rootPartName = "rootpart"; + UUID rootPartUuid = new UUID("00000000-0000-0000-0000-000000000001"); + string linkPartName = "linkpart"; + UUID linkPartUuid = new UUID("00000000-0000-0000-0001-000000000000"); + + SceneObjectPart rootPart + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = rootPartName, UUID = rootPartUuid }; + + SceneObjectPart linkPart + = new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = linkPartName, UUID = linkPartUuid }; + SceneObjectGroup linkGroup = new SceneObjectGroup(linkPart); + scene.AddNewSceneObject(linkGroup, true); + + SceneObjectGroup sog = new SceneObjectGroup(rootPart); + scene.AddNewSceneObject(sog, true); + + Assert.IsFalse(sog.GroupContainsForeignPrims); + sog.LinkToGroup(linkGroup); + Assert.IsTrue(sog.GroupContainsForeignPrims); + + scene.Backup(true); + Assert.AreEqual(1, scene.SimulationDataService.LoadObjects(scene.RegionInfo.RegionID).Count); + + // These changes should occur immediately without waiting for a backup pass + SceneObjectGroup groupToDelete = sog.DelinkFromGroup(linkPart, false); + Assert.IsFalse(groupToDelete.GroupContainsForeignPrims); + +/* backup is async + scene.DeleteSceneObject(groupToDelete, false); + + List storedObjects = scene.SimulationDataService.LoadObjects(scene.RegionInfo.RegionID); + + Assert.AreEqual(1, storedObjects.Count); + Assert.AreEqual(1, storedObjects[0].Parts.Length); + Assert.IsTrue(storedObjects[0].ContainsPart(rootPartUuid)); +*/ + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectResizeTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectResizeTests.cs new file mode 100644 index 00000000000..a587cb1f1ae --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectResizeTests.cs @@ -0,0 +1,98 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Basic scene object resize tests + /// + [TestFixture] + public class SceneObjectResizeTests : OpenSimTestCase + { + /// + /// Test resizing an object + /// + [Test] + public void TestResizeSceneObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + SceneObjectGroup g1 = SceneHelpers.AddSceneObject(scene); + + g1.GroupResize(new Vector3(2, 3, 4)); + + SceneObjectGroup g1Post = scene.GetSceneObjectGroup(g1.UUID); + + Assert.That(g1Post.RootPart.Scale.X, Is.EqualTo(2)); + Assert.That(g1Post.RootPart.Scale.Y, Is.EqualTo(3)); + Assert.That(g1Post.RootPart.Scale.Z, Is.EqualTo(4)); + +// Assert.That(g1Post.RootPart.UndoCount, Is.EqualTo(1)); + } + + /// + /// Test resizing an individual part in a scene object. + /// + [Test] + public void TestResizeSceneObjectPart() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UUID owner = UUID.Random(); + SceneObjectGroup g1 = SceneHelpers.CreateSceneObject(2, owner); + g1.RootPart.Scale = new Vector3(2, 3, 4); + g1.Parts[1].Scale = new Vector3(5, 6, 7); + + scene.AddSceneObject(g1); + + SceneObjectGroup g1Post = scene.GetSceneObjectGroup(g1.UUID); + + g1Post.Parts[1].Resize(new Vector3(8, 9, 10)); + + SceneObjectGroup g1PostPost = scene.GetSceneObjectGroup(g1.UUID); + + SceneObjectPart g1RootPart = g1PostPost.RootPart; + SceneObjectPart g1ChildPart = g1PostPost.Parts[1]; + + Assert.That(g1RootPart.Scale.X, Is.EqualTo(2)); + Assert.That(g1RootPart.Scale.Y, Is.EqualTo(3)); + Assert.That(g1RootPart.Scale.Z, Is.EqualTo(4)); + + Assert.That(g1ChildPart.Scale.X, Is.EqualTo(8)); + Assert.That(g1ChildPart.Scale.Y, Is.EqualTo(9)); + Assert.That(g1ChildPart.Scale.Z, Is.EqualTo(10)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectScriptTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectScriptTests.cs new file mode 100644 index 00000000000..7d459d52124 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectScriptTests.cs @@ -0,0 +1,71 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class SceneObjectScriptTests : OpenSimTestCase + { + [Test] + public void TestAddScript() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); +// UUID itemId = TestHelpers.ParseTail(0x2); + string itemName = "Test Script Item"; + + Scene scene = new SceneHelpers().SetupScene(); + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId); + scene.AddNewSceneObject(so, true); + + InventoryItemBase itemTemplate = new InventoryItemBase(); + itemTemplate.Name = itemName; + itemTemplate.Folder = so.UUID; + itemTemplate.InvType = (int)InventoryType.LSL; + + SceneObjectPart partWhereScriptAdded = scene.RezNewScript(userId, itemTemplate); + + Assert.That(partWhereScriptAdded, Is.Not.Null); + + IEntityInventory primInventory = partWhereScriptAdded.Inventory; + Assert.That(primInventory.GetInventoryList().Count, Is.EqualTo(1)); + Assert.That(primInventory.ContainsScripts(), Is.True); + + IList primItems = primInventory.GetInventoryItems(itemName); + Assert.That(primItems.Count, Is.EqualTo(1)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSerializationTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSerializationTests.cs new file mode 100644 index 00000000000..b55d3e845ea --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSerializationTests.cs @@ -0,0 +1,134 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Threading; +using System.Xml; +using System.Linq; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Serialization.External; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Basic scene object serialization tests. + /// + [TestFixture] + public class SceneObjectSerializationTests : OpenSimTestCase + { + + /// + /// Serialize and deserialize. + /// + [Test] + public void TestSerialDeserial() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + int partsToTestCount = 3; + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(partsToTestCount, TestHelpers.ParseTail(0x1), "obj1", 0x10); + SceneObjectPart[] parts = so.Parts; + so.Name = "obj1"; + so.Description = "xpto"; + + string xml = SceneObjectSerializer.ToXml2Format(so); + Assert.That(!string.IsNullOrEmpty(xml), "SOG serialization resulted in empty or null string"); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + XmlNodeList nodes = doc.GetElementsByTagName("SceneObjectPart"); + Assert.That(nodes.Count, Is.EqualTo(3), "SOG serialization resulted in wrong number of SOPs"); + + SceneObjectGroup so2 = SceneObjectSerializer.FromXml2Format(xml); + Assert.IsNotNull(so2, "SOG deserialization resulted in null object"); + Assert.That(so2.Name == so.Name, "Name of deserialized object does not match original name"); + Assert.That(so2.Description == so.Description, "Description of deserialized object does not match original name"); + } + + /// + /// This checks for a bug reported in mantis #7514 + /// + [Test] + public void TestNamespaceAttribute() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount account = new UserAccount(UUID.Zero, UUID.Random(), "Test", "User", string.Empty); + scene.UserAccountService.StoreUserAccount(account); + int partsToTestCount = 1; + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(partsToTestCount, TestHelpers.ParseTail(0x1), "obj1", 0x10); + SceneObjectPart[] parts = so.Parts; + so.Name = "obj1"; + so.Description = "xpto"; + so.OwnerID = account.PrincipalID; + so.RootPart.CreatorID = so.OwnerID; + + string xml = SceneObjectSerializer.ToXml2Format(so); + Assert.That(!string.IsNullOrEmpty(xml), "SOG serialization resulted in empty or null string"); + + xml = ExternalRepresentationUtils.RewriteSOP(xml, "Test Scene", "http://localhost", scene.UserAccountService, UUID.Zero); + //Console.WriteLine(xml); + + XmlDocument doc = new XmlDocument(); + doc.LoadXml(xml); + + XmlNodeList nodes = doc.GetElementsByTagName("SceneObjectPart"); + Assert.That(nodes.Count, Is.GreaterThan(0), "SOG serialization resulted in no SOPs"); + foreach (XmlAttribute a in nodes[0].Attributes) + { + int count = a.Name.Count(c => c == ':'); + Assert.That(count, Is.EqualTo(1), "Cannot have multiple ':' in attribute name in SOP"); + } + nodes = doc.GetElementsByTagName("CreatorData"); + Assert.That(nodes.Count, Is.GreaterThan(0), "SOG serialization resulted in no CreatorData"); + foreach (XmlAttribute a in nodes[0].Attributes) + { + int count = a.Name.Count(c => c == ':'); + Assert.That(count, Is.EqualTo(1), "Cannot have multiple ':' in attribute name in CreatorData"); + } + + SceneObjectGroup so2 = SceneObjectSerializer.FromXml2Format(xml); + Assert.IsNotNull(so2, "SOG deserialization resulted in null object"); + Assert.AreNotEqual(so.RootPart.CreatorIdentification, so2.RootPart.CreatorIdentification, "RewriteSOP failed to transform CreatorData."); + Assert.That(so2.RootPart.CreatorIdentification.Contains("http://"), "RewriteSOP failed to add the homeURL to CreatorData"); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSpatialTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSpatialTests.cs new file mode 100644 index 00000000000..c2c78228807 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectSpatialTests.cs @@ -0,0 +1,153 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Reflection; +using System.Threading; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Spatial scene object tests (will eventually cover root and child part position, rotation properties, etc.) + /// + [TestFixture] + public class SceneObjectSpatialTests : OpenSimTestCase + { + TestScene m_scene; + UUID m_ownerId = TestHelpers.ParseTail(0x1); + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_scene = new SceneHelpers().SetupScene(); + } + + [Test] + public void TestGetSceneObjectGroupPosition() + { + TestHelpers.InMethod(); + + Vector3 position = new Vector3(10, 20, 30); + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(1, m_ownerId, "obj1", 0x10); + so.AbsolutePosition = position; + m_scene.AddNewSceneObject(so, false); + + Assert.That(so.AbsolutePosition, Is.EqualTo(position)); + } + + [Test] + public void TestGetRootPartPosition() + { + TestHelpers.InMethod(); + + Vector3 partPosition = new Vector3(10, 20, 30); + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(1, m_ownerId, "obj1", 0x10); + so.AbsolutePosition = partPosition; + m_scene.AddNewSceneObject(so, false); + + Assert.That(so.RootPart.AbsolutePosition, Is.EqualTo(partPosition)); + Assert.That(so.RootPart.GroupPosition, Is.EqualTo(partPosition)); + Assert.That(so.RootPart.GetWorldPosition(), Is.EqualTo(partPosition)); + Assert.That(so.RootPart.RelativePosition, Is.EqualTo(partPosition)); + Assert.That(so.RootPart.OffsetPosition, Is.EqualTo(Vector3.Zero)); + } + + [Test] + public void TestGetChildPartPosition() + { + TestHelpers.InMethod(); + + Vector3 rootPartPosition = new Vector3(10, 20, 30); + Vector3 childOffsetPosition = new Vector3(2, 3, 4); + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(2, m_ownerId, "obj1", 0x10); + so.AbsolutePosition = rootPartPosition; + so.Parts[1].OffsetPosition = childOffsetPosition; + + m_scene.AddNewSceneObject(so, false); + + // Calculate child absolute position. + Vector3 childPosition = new Vector3(rootPartPosition + childOffsetPosition); + + SceneObjectPart childPart = so.Parts[1]; + Assert.That(childPart.AbsolutePosition, Is.EqualTo(childPosition)); + Assert.That(childPart.GroupPosition, Is.EqualTo(rootPartPosition)); + Assert.That(childPart.GetWorldPosition(), Is.EqualTo(childPosition)); + Assert.That(childPart.RelativePosition, Is.EqualTo(childOffsetPosition)); + Assert.That(childPart.OffsetPosition, Is.EqualTo(childOffsetPosition)); + } + + [Test] + public void TestGetChildPartPositionAfterObjectRotation() + { + TestHelpers.InMethod(); + + Vector3 rootPartPosition = new Vector3(10, 20, 30); + Vector3 childOffsetPosition = new Vector3(2, 3, 4); + + SceneObjectGroup so + = SceneHelpers.CreateSceneObject(2, m_ownerId, "obj1", 0x10); + so.AbsolutePosition = rootPartPosition; + so.Parts[1].OffsetPosition = childOffsetPosition; + + m_scene.AddNewSceneObject(so, false); + + so.UpdateGroupRotationR(Quaternion.CreateFromEulers(0, 0, -90 * Utils.DEG_TO_RAD)); + + // Calculate child absolute position. + Vector3 rotatedChildOffsetPosition + = new Vector3(childOffsetPosition.Y, -childOffsetPosition.X, childOffsetPosition.Z); + + Vector3 childPosition = new Vector3(rootPartPosition + rotatedChildOffsetPosition); + + SceneObjectPart childPart = so.Parts[1]; + + Assert.That(childPart.AbsolutePosition, Is.EqualTo(childPosition)); + + Assert.That(childPart.GroupPosition, Is.EqualTo(rootPartPosition)); + Assert.That(childPart.GetWorldPosition(), Is.EqualTo(childPosition)); + + // Relative to root part as (0, 0, 0) + Assert.That(childPart.RelativePosition, Is.EqualTo(childOffsetPosition)); + + // Relative to root part as (0, 0, 0) + Assert.That(childPart.OffsetPosition, Is.EqualTo(childOffsetPosition)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectStatusTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectStatusTests.cs new file mode 100644 index 00000000000..8d880830799 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectStatusTests.cs @@ -0,0 +1,242 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Basic scene object status tests + /// + [TestFixture] + public class SceneObjectStatusTests : OpenSimTestCase + { + private TestScene m_scene; + private UUID m_ownerId = TestHelpers.ParseTail(0x1); + private SceneObjectGroup m_so1; + private SceneObjectGroup m_so2; + + [SetUp] + public void Init() + { + m_scene = new SceneHelpers().SetupScene(); + m_so1 = SceneHelpers.CreateSceneObject(1, m_ownerId, "so1", 0x10); + m_so2 = SceneHelpers.CreateSceneObject(1, m_ownerId, "so2", 0x20); + } + + [Test] + public void TestSetTemporary() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + m_so1.ScriptSetTemporaryStatus(true); + + // Is this really the correct flag? + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.TemporaryOnRez)); + Assert.That(m_so1.Backup, Is.False); + + // Test setting back to non-temporary + m_so1.ScriptSetTemporaryStatus(false); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.None)); + Assert.That(m_so1.Backup, Is.True); + } + + [Test] + public void TestSetPhantomSinglePrim() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + + SceneObjectPart rootPart = m_so1.RootPart; + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + + m_so1.ScriptSetPhantomStatus(true); + +// Console.WriteLine("so.RootPart.Flags [{0}]", so.RootPart.Flags); + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.Phantom)); + + m_so1.ScriptSetPhantomStatus(false); + + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + } + + [Test] + public void TestSetNonPhysicsVolumeDetectSinglePrim() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + + SceneObjectPart rootPart = m_so1.RootPart; + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + + m_so1.ScriptSetVolumeDetect(true); + +// Console.WriteLine("so.RootPart.Flags [{0}]", so.RootPart.Flags); + // PrimFlags.JointLP2P is incorrect it now means VolumeDetect (as defined by viewers) + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.Phantom | PrimFlags.JointLP2P)); + + m_so1.ScriptSetVolumeDetect(false); + + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + } + + [Test] + public void TestSetPhysicsSinglePrim() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + + SceneObjectPart rootPart = m_so1.RootPart; + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + + m_so1.ScriptSetPhysicsStatus(true); + + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.Physics)); + + m_so1.ScriptSetPhysicsStatus(false); + + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + } + + [Test] + public void TestSetPhysicsVolumeDetectSinglePrim() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + + SceneObjectPart rootPart = m_so1.RootPart; + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.None)); + + m_so1.ScriptSetPhysicsStatus(true); + m_so1.ScriptSetVolumeDetect(true); + + // PrimFlags.JointLP2P is incorrect it now means VolumeDetect (as defined by viewers) + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.Phantom | PrimFlags.Physics | PrimFlags.JointLP2P)); + + m_so1.ScriptSetVolumeDetect(false); + + Assert.That(rootPart.Flags, Is.EqualTo(PrimFlags.Physics)); + } + + [Test] + public void TestSetPhysicsLinkset() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + m_scene.AddSceneObject(m_so2); + + m_scene.LinkObjects(m_ownerId, m_so1.LocalId, new List() { m_so2.LocalId }); + + m_so1.ScriptSetPhysicsStatus(true); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.Physics)); + Assert.That(m_so1.Parts[1].Flags, Is.EqualTo(PrimFlags.Physics)); + + m_so1.ScriptSetPhysicsStatus(false); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.None)); + Assert.That(m_so1.Parts[1].Flags, Is.EqualTo(PrimFlags.None)); + + m_so1.ScriptSetPhysicsStatus(true); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.Physics)); + Assert.That(m_so1.Parts[1].Flags, Is.EqualTo(PrimFlags.Physics)); + } + + /// + /// Test that linking results in the correct physical status for all linkees. + /// + [Test] + public void TestLinkPhysicsBothPhysical() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + m_scene.AddSceneObject(m_so2); + + m_so1.ScriptSetPhysicsStatus(true); + m_so2.ScriptSetPhysicsStatus(true); + + m_scene.LinkObjects(m_ownerId, m_so1.LocalId, new List() { m_so2.LocalId }); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.Physics)); + Assert.That(m_so1.Parts[1].Flags, Is.EqualTo(PrimFlags.Physics)); + } + + /// + /// Test that linking results in the correct physical status for all linkees. + /// + [Test] + public void TestLinkPhysicsRootPhysicalOnly() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + m_scene.AddSceneObject(m_so2); + + m_so1.ScriptSetPhysicsStatus(true); + + m_scene.LinkObjects(m_ownerId, m_so1.LocalId, new List() { m_so2.LocalId }); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.Physics)); + Assert.That(m_so1.Parts[1].Flags, Is.EqualTo(PrimFlags.Physics)); + } + + /// + /// Test that linking results in the correct physical status for all linkees. + /// + [Test] + public void TestLinkPhysicsChildPhysicalOnly() + { + TestHelpers.InMethod(); + + m_scene.AddSceneObject(m_so1); + m_scene.AddSceneObject(m_so2); + + m_so2.ScriptSetPhysicsStatus(true); + + m_scene.LinkObjects(m_ownerId, m_so1.LocalId, new List() { m_so2.LocalId }); + + Assert.That(m_so1.RootPart.Flags, Is.EqualTo(PrimFlags.None)); + Assert.That(m_so1.Parts[1].Flags, Is.EqualTo(PrimFlags.None)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUndoRedoTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUndoRedoTests.cs new file mode 100644 index 00000000000..340da9cef3d --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUndoRedoTests.cs @@ -0,0 +1,184 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* undo has changed, this tests dont apply without large changes +using System; +using System.Reflection; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Tests for undo/redo + /// + public class SceneObjectUndoRedoTests : OpenSimTestCase + { + [Test] + public void TestUndoRedoResizeSceneObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Vector3 firstSize = new Vector3(2, 3, 4); + Vector3 secondSize = new Vector3(5, 6, 7); + + Scene scene = new SceneHelpers().SetupScene(); + scene.MaxUndoCount = 20; + SceneObjectGroup g1 = SceneHelpers.AddSceneObject(scene); + + // TODO: It happens to be the case that we are not storing undo states for SOPs which are not yet in a SOG, + // which is the way that AddSceneObject() sets up the object (i.e. it creates the SOP first). However, + // this is somewhat by chance. Really, we shouldn't be storing undo states at all if the object is not + // in a scene. + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(0)); + + g1.GroupResize(firstSize); + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(1)); + + g1.GroupResize(secondSize); + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(2)); + + g1.RootPart.Undo(); + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(1)); + Assert.That(g1.GroupScale, Is.EqualTo(firstSize)); + + g1.RootPart.Redo(); + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(2)); + Assert.That(g1.GroupScale, Is.EqualTo(secondSize)); + } + + [Test] + public void TestUndoLimit() + { + TestHelpers.InMethod(); + + Vector3 firstSize = new Vector3(2, 3, 4); + Vector3 secondSize = new Vector3(5, 6, 7); + Vector3 thirdSize = new Vector3(8, 9, 10); + Vector3 fourthSize = new Vector3(11, 12, 13); + + Scene scene = new SceneHelpers().SetupScene(); + scene.MaxUndoCount = 2; + SceneObjectGroup g1 = SceneHelpers.AddSceneObject(scene); + + g1.GroupResize(firstSize); + g1.GroupResize(secondSize); + g1.GroupResize(thirdSize); + g1.GroupResize(fourthSize); + + g1.RootPart.Undo(); + g1.RootPart.Undo(); + g1.RootPart.Undo(); + + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(0)); + Assert.That(g1.GroupScale, Is.EqualTo(secondSize)); + } + + [Test] + public void TestNoUndoOnObjectsNotInScene() + { + TestHelpers.InMethod(); + + Vector3 firstSize = new Vector3(2, 3, 4); + Vector3 secondSize = new Vector3(5, 6, 7); +// Vector3 thirdSize = new Vector3(8, 9, 10); +// Vector3 fourthSize = new Vector3(11, 12, 13); + + Scene scene = new SceneHelpers().SetupScene(); + scene.MaxUndoCount = 20; + SceneObjectGroup g1 = SceneHelpers.CreateSceneObject(1, TestHelpers.ParseTail(0x1)); + + g1.GroupResize(firstSize); + g1.GroupResize(secondSize); + + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(0)); + + g1.RootPart.Undo(); + + Assert.That(g1.GroupScale, Is.EqualTo(secondSize)); + } + + [Test] + public void TestUndoBeyondAvailable() + { + TestHelpers.InMethod(); + + Vector3 newSize = new Vector3(2, 3, 4); + + Scene scene = new SceneHelpers().SetupScene(); + scene.MaxUndoCount = 20; + SceneObjectGroup g1 = SceneHelpers.AddSceneObject(scene); + Vector3 originalSize = g1.GroupScale; + + g1.RootPart.Undo(); + + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(0)); + Assert.That(g1.GroupScale, Is.EqualTo(originalSize)); + + g1.GroupResize(newSize); + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(1)); + Assert.That(g1.GroupScale, Is.EqualTo(newSize)); + + g1.RootPart.Undo(); + g1.RootPart.Undo(); + + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(0)); + Assert.That(g1.GroupScale, Is.EqualTo(originalSize)); + } + + [Test] + public void TestRedoBeyondAvailable() + { + TestHelpers.InMethod(); + + Vector3 newSize = new Vector3(2, 3, 4); + + Scene scene = new SceneHelpers().SetupScene(); + scene.MaxUndoCount = 20; + SceneObjectGroup g1 = SceneHelpers.AddSceneObject(scene); + Vector3 originalSize = g1.GroupScale; + + g1.RootPart.Redo(); + + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(0)); + Assert.That(g1.GroupScale, Is.EqualTo(originalSize)); + + g1.GroupResize(newSize); + g1.RootPart.Undo(); + g1.RootPart.Redo(); + g1.RootPart.Redo(); + + Assert.That(g1.RootPart.UndoCount, Is.EqualTo(1)); + Assert.That(g1.GroupScale, Is.EqualTo(newSize)); + } + } +} +*/ \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUserGroupTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUserGroupTests.cs new file mode 100644 index 00000000000..549a67ef711 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneObjectUserGroupTests.cs @@ -0,0 +1,77 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class SceneObjectUserGroupTests + { + /// + /// Test share with group object functionality + /// + /// This test is not yet fully implemented + [Test] + public void TestShareWithGroup() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = UUID.Parse("10000000-0000-0000-0000-000000000001"); + + TestScene scene = new SceneHelpers().SetupScene(); + IConfigSource configSource = new IniConfigSource(); + + IConfig startupConfig = configSource.AddConfig("Startup"); + startupConfig.Set("serverside_object_permissions", true); + + IConfig groupsConfig = configSource.AddConfig("Groups"); + groupsConfig.Set("Enabled", true); + groupsConfig.Set("Module", "GroupsModule"); + groupsConfig.Set("DebugEnabled", true); + + SceneHelpers.SetupSceneModules( + scene, configSource, new object[] + { new DefaultPermissionsModule(), + new GroupsModule(), + new MockGroupsServicesConnector() }); + + IClientAPI client = SceneHelpers.AddScenePresence(scene, userId).ControllingClient; + + IGroupsModule groupsModule = scene.RequestModuleInterface(); + + groupsModule.CreateGroup(client, "group1", "To boldly go", true, UUID.Zero, 5, true, true, true); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAgentTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAgentTests.cs new file mode 100644 index 00000000000..00c493c5f37 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAgentTests.cs @@ -0,0 +1,290 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Timers; +using Timer = System.Timers.Timer; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Tests.Common; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene presence tests + /// + [TestFixture] + public class ScenePresenceAgentTests : OpenSimTestCase + { +// public Scene scene, scene2, scene3; +// public UUID agent1, agent2, agent3; +// public static Random random; +// public ulong region1, region2, region3; +// public AgentCircuitData acd1; +// public TestClient testclient; + +// [TestFixtureSetUp] +// public void Init() +// { +//// TestHelpers.InMethod(); +//// +//// SceneHelpers sh = new SceneHelpers(); +//// +//// scene = sh.SetupScene("Neighbour x", UUID.Random(), 1000, 1000); +//// scene2 = sh.SetupScene("Neighbour x+1", UUID.Random(), 1001, 1000); +//// scene3 = sh.SetupScene("Neighbour x-1", UUID.Random(), 999, 1000); +//// +//// ISharedRegionModule interregionComms = new LocalSimulationConnectorModule(); +//// interregionComms.Initialise(new IniConfigSource()); +//// interregionComms.PostInitialise(); +//// SceneHelpers.SetupSceneModules(scene, new IniConfigSource(), interregionComms); +//// SceneHelpers.SetupSceneModules(scene2, new IniConfigSource(), interregionComms); +//// SceneHelpers.SetupSceneModules(scene3, new IniConfigSource(), interregionComms); +// +//// agent1 = UUID.Random(); +//// agent2 = UUID.Random(); +//// agent3 = UUID.Random(); +// +//// region1 = scene.RegionInfo.RegionHandle; +//// region2 = scene2.RegionInfo.RegionHandle; +//// region3 = scene3.RegionInfo.RegionHandle; +// } + + [Test] + public void TestCreateRootScenePresence() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + TestScene scene = new SceneHelpers().SetupScene(); + SceneHelpers.AddScenePresence(scene, spUuid); + + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(spUuid), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + ScenePresence sp = scene.GetScenePresence(spUuid); + Assert.That(sp, Is.Not.Null); + Assert.That(sp.IsChildAgent, Is.False); + Assert.That(sp.UUID, Is.EqualTo(spUuid)); + + Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); + } + + /// + /// Test that duplicate complete movement calls are ignored. + /// + /// + /// If duplicate calls are not ignored then there is a risk of race conditions or other unexpected effects. + /// + [Test] + public void TestDupeCompleteMovementCalls() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + TestScene scene = new SceneHelpers().SetupScene(); + + int makeRootAgentEvents = 0; + scene.EventManager.OnMakeRootAgent += spi => makeRootAgentEvents++; + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, spUuid); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Normally these would be invoked by a CompleteMovement message coming in to the UDP stack. But for + // convenience, here we will invoke it manually. + sp.CompleteMovement(sp.ControllingClient, true); + + Assert.That(makeRootAgentEvents, Is.EqualTo(1)); + + // Check rest of exepcted parameters. + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(spUuid), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + Assert.That(sp.IsChildAgent, Is.False); + Assert.That(sp.UUID, Is.EqualTo(spUuid)); + + Assert.That(scene.GetScenePresences().Count, Is.EqualTo(1)); + } + + [Test] + public void TestCreateDuplicateRootScenePresence() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + // The etm is only invoked by this test to check whether an agent is still in transit if there is a dupe + EntityTransferModule etm = new EntityTransferModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + + TestScene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, config, etm); + SceneHelpers.AddScenePresence(scene, spUuid); + SceneHelpers.AddScenePresence(scene, spUuid); + + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(spUuid), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + ScenePresence sp = scene.GetScenePresence(spUuid); + Assert.That(sp, Is.Not.Null); + Assert.That(sp.IsChildAgent, Is.False); + Assert.That(sp.UUID, Is.EqualTo(spUuid)); + } + + [Test] + public void TestCloseClient() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + + scene.CloseAgent(sp.UUID, false); + + Assert.That(scene.GetScenePresence(sp.UUID), Is.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(sp.UUID), Is.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(0)); + +// TestHelpers.DisableLogging(); + } + + [Test] + public void TestCreateChildScenePresence() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + LocalSimulationConnectorModule lsc = new LocalSimulationConnectorModule(); + + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Modules"); + config.Set("SimulationServices", "LocalSimulationConnectorModule"); + + SceneHelpers sceneHelpers = new SceneHelpers(); + TestScene scene = sceneHelpers.SetupScene(); + SceneHelpers.SetupSceneModules(scene, configSource, lsc); + + UUID agentId = TestHelpers.ParseTail(0x01); + AgentCircuitData acd = SceneHelpers.GenerateAgentData(agentId); + acd.child = true; + + GridRegion region = scene.GridService.GetRegionByName(UUID.Zero, scene.RegionInfo.RegionName); + string reason; + + // *** This is the first stage, when a neighbouring region is told that a viewer is about to try and + // establish a child scene presence. We pass in the circuit code that the client has to connect with *** + // XXX: ViaLogin may not be correct here. + EntityTransferContext ctx = new EntityTransferContext(); + scene.SimulationService.CreateAgent(null, region, acd, (uint)TeleportFlags.ViaLogin, ctx, out reason); + + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(agentId), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + // There's no scene presence yet since only an agent circuit has been established. + Assert.That(scene.GetScenePresence(agentId), Is.Null); + + // *** This is the second stage, where the client established a child agent/scene presence using the + // circuit code given to the scene in stage 1 *** + TestClient client = new TestClient(acd, scene); + scene.AddNewAgent(client, PresenceType.User); + + Assert.That(scene.AuthenticateHandler.GetAgentCircuitData(agentId), Is.Not.Null); + Assert.That(scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + + ScenePresence sp = scene.GetScenePresence(agentId); + Assert.That(sp, Is.Not.Null); + Assert.That(sp.UUID, Is.EqualTo(agentId)); + Assert.That(sp.IsChildAgent, Is.True); + } + + /// + /// Test that if a root agent logs into a region, a child agent is also established in the neighbouring region + /// + /// + /// Please note that unlike the other tests here, this doesn't rely on anything set up in the instance fields. + /// INCOMPLETE + /// + [Test] + public void TestChildAgentEstablishedInNeighbour() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + +// UUID agent1Id = UUID.Parse("00000000-0000-0000-0000-000000000001"); + + TestScene myScene1 = new SceneHelpers().SetupScene("Neighbour y", UUID.Random(), 1000, 1000); + TestScene myScene2 = new SceneHelpers().SetupScene("Neighbour y + 1", UUID.Random(), 1001, 1000); + + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Startup"); + config.Set("serverside_object_permissions", true); + + EntityTransferModule etm = new EntityTransferModule(); + + EventQueueGetModule eqgm1 = new EventQueueGetModule(); + SceneHelpers.SetupSceneModules(myScene1, configSource, etm, eqgm1); + + EventQueueGetModule eqgm2 = new EventQueueGetModule(); + SceneHelpers.SetupSceneModules(myScene2, configSource, etm, eqgm2); + +// SceneHelpers.AddScenePresence(myScene1, agent1Id); +// ScenePresence childPresence = myScene2.GetScenePresence(agent1); +// +// // TODO: Need to do a fair amount of work to allow synchronous establishment of child agents +// Assert.That(childPresence, Is.Not.Null); +// Assert.That(childPresence.IsChildAgent, Is.True); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAnimationTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAnimationTests.cs new file mode 100644 index 00000000000..50e043c2b23 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAnimationTests.cs @@ -0,0 +1,52 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene presence animation tests + /// + [TestFixture] + public class ScenePresenceAnimationTests : OpenSimTestCase + { + [Test] + public void TestFlyingAnimation() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + sp.Flying = true; + sp.Animator.UpdateMovementAnimations(); + + Assert.That(sp.Animator.CurrentMovementAnimation, Is.EqualTo("HOVER")); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAutopilotTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAutopilotTests.cs new file mode 100644 index 00000000000..12ae54264d7 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceAutopilotTests.cs @@ -0,0 +1,124 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class ScenePresenceAutopilotTests : OpenSimTestCase + { + private TestScene m_scene; + + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.None; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten not to worry about such things. + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public void Init() + { + m_scene = new SceneHelpers().SetupScene(); + } + + [Test] + public void TestMove() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + + Vector3 startPos = sp.AbsolutePosition; +// Vector3 startPos = new Vector3(128, 128, 30); + + // For now, we'll make the scene presence fly to simplify this test, but this needs to change. + sp.Flying = true; + + m_scene.Update(1); + Assert.That(sp.AbsolutePosition, Is.EqualTo(startPos)); + + Vector3 targetPos = startPos + new Vector3(0, 10, 0); + sp.MoveToTarget(targetPos, false, false, false); + + Assert.That(sp.AbsolutePosition, Is.EqualTo(startPos)); + Assert.That( + sp.Rotation, new QuaternionToleranceConstraint(new Quaternion(0, 0, 0.7071068f, 0.7071068f), 0.000001)); + + m_scene.Update(1); + + // We should really check the exact figure. + Assert.That(sp.AbsolutePosition.X, Is.EqualTo(startPos.X)); + Assert.That(sp.AbsolutePosition.Y, Is.GreaterThan(startPos.Y)); + Assert.That(sp.AbsolutePosition.Z, Is.EqualTo(startPos.Z)); + Assert.That(sp.AbsolutePosition.Z, Is.LessThan(targetPos.X)); + + m_scene.Update(50); + + double distanceToTarget = Util.GetDistanceTo(sp.AbsolutePosition, targetPos); + Assert.That(distanceToTarget, Is.LessThan(1), "Avatar not within 1 unit of target position on first move"); + Assert.That(sp.AbsolutePosition, Is.EqualTo(targetPos)); + Assert.That(sp.AgentControlFlags, Is.EqualTo((uint)AgentManager.ControlFlags.NONE)); + + // Try a second movement + startPos = sp.AbsolutePosition; + targetPos = startPos + new Vector3(10, 0, 0); + sp.MoveToTarget(targetPos, false, false, false); + + Assert.That(sp.AbsolutePosition, Is.EqualTo(startPos)); + Assert.That( + sp.Rotation, new QuaternionToleranceConstraint(new Quaternion(0, 0, 0, 1), 0.000001)); + + m_scene.Update(1); + + // We should really check the exact figure. + Assert.That(sp.AbsolutePosition.X, Is.GreaterThan(startPos.X)); + Assert.That(sp.AbsolutePosition.X, Is.LessThan(targetPos.X)); + Assert.That(sp.AbsolutePosition.Y, Is.EqualTo(startPos.Y)); + Assert.That(sp.AbsolutePosition.Z, Is.EqualTo(startPos.Z)); + + m_scene.Update(50); + + distanceToTarget = Util.GetDistanceTo(sp.AbsolutePosition, targetPos); + Assert.That(distanceToTarget, Is.LessThan(1), "Avatar not within 1 unit of target position on second move"); + Assert.That(sp.AbsolutePosition, Is.EqualTo(targetPos)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCapabilityTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCapabilityTests.cs new file mode 100644 index 00000000000..1c35e6b3cc9 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCapabilityTests.cs @@ -0,0 +1,87 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Timers; +using Timer = System.Timers.Timer; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Tests.Common; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class ScenePresenceCapabilityTests : OpenSimTestCase + { + [Test] + public void TestChildAgentSingleRegionCapabilities() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID spUuid = TestHelpers.ParseTail(0x1); + + // XXX: This is not great since the use of statics will mean that this has to be manually cleaned up for + // any subsequent test. + // XXX: May replace with a mock IHttpServer later. + BaseHttpServer httpServer = new BaseHttpServer(99999); + MainServer.AddHttpServer(httpServer); + MainServer.Instance = httpServer; + + CapabilitiesModule capsMod = new CapabilitiesModule(); + TestScene scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(scene, capsMod); + + ScenePresence sp = SceneHelpers.AddChildScenePresence(scene, spUuid); + //Assert.That(capsMod.GetCapsForUser(spUuid), Is.Not.Null); + + // TODO: Need to add tests for other ICapabiltiesModule methods. + +// scene.IncomingCloseAgent(sp.UUID, false); +// //Assert.That(capsMod.GetCapsForUser(spUuid), Is.Null); + scene.CloseAgent(sp.UUID, false); +// Assert.That(capsMod.GetCapsForUser(spUuid), Is.Null); + + // TODO: Need to add tests for other ICapabiltiesModule methods. + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCrossingTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCrossingTests.cs new file mode 100644 index 00000000000..da756b0b515 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceCrossingTests.cs @@ -0,0 +1,246 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Tests.Common; +using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; +using System.Threading; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class ScenePresenceCrossingTests : OpenSimTestCase + { + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [Test] + public void TestCrossOnSameSimulator() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + +// TestEventQueueGetModule eqmA = new TestEventQueueGetModule(); + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); +// IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. +// entityTransferConfig.Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); +// SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA, eqmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + originalSp.AbsolutePosition = new Vector3(128, 32, 10); + +// originalSp.Flying = true; + +// Console.WriteLine("First pos {0}", originalSp.AbsolutePosition); + +// eqmA.ClearEvents(); + + AgentUpdateArgs moveArgs = new AgentUpdateArgs(); + //moveArgs.BodyRotation = Quaternion.CreateFromEulers(Vector3.Zero); + moveArgs.BodyRotation = Quaternion.CreateFromEulers(new Vector3(0, 0, (float)-(Math.PI / 2))); + moveArgs.ControlFlags = (uint)(AgentManager.ControlFlags.AGENT_CONTROL_AT_POS | AgentManager.ControlFlags.AGENT_CONTROL_FAST_AT); + + originalSp.HandleAgentUpdate(originalSp.ControllingClient, moveArgs); + + sceneA.Update(1); + +// Console.WriteLine("Second pos {0}", originalSp.AbsolutePosition); + + // FIXME: This is a sufficient number of updates to for the presence to reach the northern border. + // But really we want to do this in a more robust way. + for (int i = 0; i < 100; i++) + { + sceneA.Update(1); +// Console.WriteLine("Pos {0}", originalSp.AbsolutePosition); + } + + // Need to sort processing of EnableSimulator message on adding scene presences before we can test eqm + // messages +// Dictionary> eqmEvents = eqmA.Events; +// +// Assert.That(eqmEvents.Count, Is.EqualTo(1)); +// Assert.That(eqmEvents.ContainsKey(originalSp.UUID), Is.True); +// +// List spEqmEvents = eqmEvents[originalSp.UUID]; +// +// Assert.That(spEqmEvents.Count, Is.EqualTo(1)); +// Assert.That(spEqmEvents[0].Name, Is.EqualTo("CrossRegion")); + + // sceneA should now only have a child agent + ScenePresence spAfterCrossSceneA = sceneA.GetScenePresence(originalSp.UUID); + Assert.That(spAfterCrossSceneA.IsChildAgent, Is.True); + + ScenePresence spAfterCrossSceneB = sceneB.GetScenePresence(originalSp.UUID); + + // Agent remains a child until the client triggers complete movement + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.True); + + TestClient sceneBTc = ((TestClient)spAfterCrossSceneB.ControllingClient); + + int agentMovementCompleteReceived = 0; + sceneBTc.OnReceivedMoveAgentIntoRegion += (ri, pos, look) => agentMovementCompleteReceived++; + + sceneBTc.CompleteMovement(); + + Assert.That(agentMovementCompleteReceived, Is.EqualTo(1)); + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.False); + } + + /// + /// Test a cross attempt where the user can see into the neighbour but does not have permission to become + /// root there. + /// + [Test] + public void TestCrossOnSameSimulatorNoRootDestPerm() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1000, 999); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + + // We need to set up the permisions module on scene B so that our later use of agent limit to deny + // QueryAccess won't succeed anyway because administrators are always allowed in and the default + // IsAdministrator if no permissions module is present is true. + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), new DefaultPermissionsModule(), etmB); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + // Make sure sceneB will not accept this avatar. + sceneB.RegionInfo.EstateSettings.PublicAccess = false; + + ScenePresence originalSp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + originalSp.AbsolutePosition = new Vector3(128, 32, 10); + + AgentUpdateArgs moveArgs = new AgentUpdateArgs(); + //moveArgs.BodyRotation = Quaternion.CreateFromEulers(Vector3.Zero); + moveArgs.BodyRotation = Quaternion.CreateFromEulers(new Vector3(0, 0, (float)-(Math.PI / 2))); + moveArgs.ControlFlags = (uint)(AgentManager.ControlFlags.AGENT_CONTROL_AT_POS | AgentManager.ControlFlags.AGENT_CONTROL_FAST_AT); + + originalSp.HandleAgentUpdate(originalSp.ControllingClient, moveArgs); + + sceneA.Update(1); + +// Console.WriteLine("Second pos {0}", originalSp.AbsolutePosition); + + // FIXME: This is a sufficient number of updates to for the presence to reach the northern border. + // But really we want to do this in a more robust way. + for (int i = 0; i < 100; i++) + { + sceneA.Update(1); +// Console.WriteLine("Pos {0}", originalSp.AbsolutePosition); + } + + // sceneA agent should still be root + ScenePresence spAfterCrossSceneA = sceneA.GetScenePresence(originalSp.UUID); + Assert.That(spAfterCrossSceneA.IsChildAgent, Is.False); + + ScenePresence spAfterCrossSceneB = sceneB.GetScenePresence(originalSp.UUID); + + // sceneB agent should still be child + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.True); + + // sceneB should ignore unauthorized attempt to upgrade agent to root + TestClient sceneBTc = ((TestClient)spAfterCrossSceneB.ControllingClient); + + int agentMovementCompleteReceived = 0; + sceneBTc.OnReceivedMoveAgentIntoRegion += (ri, pos, look) => agentMovementCompleteReceived++; + + sceneBTc.CompleteMovement(); + + Assert.That(agentMovementCompleteReceived, Is.EqualTo(0)); + Assert.That(spAfterCrossSceneB.IsChildAgent, Is.True); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceSitTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceSitTests.cs new file mode 100644 index 00000000000..cdadf0f984e --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceSitTests.cs @@ -0,0 +1,242 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class ScenePresenceSitTests : OpenSimTestCase + { + private TestScene m_scene; + private ScenePresence m_sp; + + [SetUp] + public void Init() + { + m_scene = new SceneHelpers().SetupScene(); + m_sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + } + + [Test] + public void TestSitOutsideRangeNoTarget() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // More than 10 meters away from 0, 0, 0 (default part position) + Vector3 startPos = new Vector3(10.1f, 0, 0); + m_sp.AbsolutePosition = startPos; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + + m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(0)); + Assert.That(part.GetSittingAvatars(), Is.Null); + Assert.That(m_sp.ParentID, Is.EqualTo(0)); + Assert.AreEqual(startPos, m_sp.AbsolutePosition); + } + + [Test] + public void TestSitWithinRangeNoTarget() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Less than 10 meters away from 0, 0, 0 (default part position) + Vector3 startPos = new Vector3(9.9f, 0, 0); + m_sp.AbsolutePosition = startPos; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + + // We need to preserve this here because phys actor is removed by the sit. + Vector3 spPhysActorSize = m_sp.PhysicsActor.Size; + m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); + + Assert.That(m_sp.PhysicsActor, Is.Null); + + Assert.That( + m_sp.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, spPhysActorSize.Z / 2))); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(1)); + HashSet sittingAvatars = part.GetSittingAvatars(); + Assert.That(sittingAvatars.Count, Is.EqualTo(1)); + Assert.That(sittingAvatars.Contains(m_sp)); + Assert.That(m_sp.ParentID, Is.EqualTo(part.LocalId)); + } + + [Test] + public void TestSitAndStandWithNoSitTarget() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Make sure we're within range to sit + Vector3 startPos = new Vector3(1, 1, 1); + m_sp.AbsolutePosition = startPos; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + + // We need to preserve this here because phys actor is removed by the sit. + Vector3 spPhysActorSize = m_sp.PhysicsActor.Size; + m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); + + Assert.That( + m_sp.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, spPhysActorSize.Z / 2))); + + m_sp.StandUp(); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(0)); + Assert.That(part.GetSittingAvatars(), Is.Null); + Assert.That(m_sp.ParentID, Is.EqualTo(0)); + Assert.That(m_sp.PhysicsActor, Is.Not.Null); + } + + [Test] + public void TestSitAndStandWithNoSitTargetChildPrim() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Make sure we're within range to sit + Vector3 startPos = new Vector3(1, 1, 1); + m_sp.AbsolutePosition = startPos; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene, 2, m_sp.UUID, "part", 0x10).Parts[1]; + part.OffsetPosition = new Vector3(2, 3, 4); + + // We need to preserve this here because phys actor is removed by the sit. + Vector3 spPhysActorSize = m_sp.PhysicsActor.Size; + m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); + + Assert.That( + m_sp.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, spPhysActorSize.Z / 2))); + + m_sp.StandUp(); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(0)); + Assert.That(part.GetSittingAvatars(), Is.Null); + Assert.That(m_sp.ParentID, Is.EqualTo(0)); + Assert.That(m_sp.PhysicsActor, Is.Not.Null); + } + + [Test] + public void TestSitAndStandWithSitTarget() + { +/* sit position math as changed, this needs to be fixed later + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // If a prim has a sit target then we can sit from any distance away + Vector3 startPos = new Vector3(128, 128, 30); + m_sp.AbsolutePosition = startPos; + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + part.SitTargetPosition = new Vector3(0, 0, 1); + + m_sp.HandleAgentRequestSit(m_sp.ControllingClient, m_sp.UUID, part.UUID, Vector3.Zero); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(m_sp.UUID)); + Assert.That(m_sp.ParentID, Is.EqualTo(part.LocalId)); + + // This section is copied from ScenePresence.HandleAgentSit(). Correctness is not guaranteed. + double x, y, z, m1, m2; + + Quaternion r = part.SitTargetOrientation;; + m1 = r.X * r.X + r.Y * r.Y; + m2 = r.Z * r.Z + r.W * r.W; + + // Rotate the vector <0, 0, 1> + x = 2 * (r.X * r.Z + r.Y * r.W); + y = 2 * (-r.X * r.W + r.Y * r.Z); + z = m2 - m1; + + // Set m to be the square of the norm of r. + double m = m1 + m2; + + // This constant is emperically determined to be what is used in SL. + // See also http://opensimulator.org/mantis/view.php?id=7096 + double offset = 0.05; + + Vector3 up = new Vector3((float)x, (float)y, (float)z); + Vector3 sitOffset = up * (float)offset; + // End of copied section. + + Assert.That( + m_sp.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + part.SitTargetPosition - sitOffset + ScenePresence.SIT_TARGET_ADJUSTMENT)); + Assert.That(m_sp.PhysicsActor, Is.Null); + + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(1)); + HashSet sittingAvatars = part.GetSittingAvatars(); + Assert.That(sittingAvatars.Count, Is.EqualTo(1)); + Assert.That(sittingAvatars.Contains(m_sp)); + + m_sp.StandUp(); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(m_sp.ParentID, Is.EqualTo(0)); + Assert.That(m_sp.PhysicsActor, Is.Not.Null); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(part.GetSittingAvatarsCount(), Is.EqualTo(0)); + Assert.That(part.GetSittingAvatars(), Is.Null); +*/ + } + + [Test] + public void TestSitAndStandOnGround() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // If a prim has a sit target then we can sit from any distance away +// Vector3 startPos = new Vector3(128, 128, 30); +// sp.AbsolutePosition = startPos; + + m_sp.HandleAgentSitOnGround(); + + Assert.That(m_sp.SitGround, Is.True); + Assert.That(m_sp.PhysicsActor, Is.Null); + + m_sp.StandUp(); + + Assert.That(m_sp.SitGround, Is.False); + Assert.That(m_sp.PhysicsActor, Is.Not.Null); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceTeleportTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceTeleportTests.cs new file mode 100644 index 00000000000..69da4b2290f --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/ScenePresenceTeleportTests.cs @@ -0,0 +1,685 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using OpenSim.Framework; + +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.CoreModules.Framework.EntityTransfer; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Teleport tests in a standalone OpenSim + /// + [TestFixture] + public class ScenePresenceTeleportTests : OpenSimTestCase + { + [OneTimeSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [Test] + public void TestSameRegion() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EntityTransferModule etm = new EntityTransferModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + // Not strictly necessary since FriendsModule assumes it is the default (!) + config.Configs["Modules"].Set("EntityTransferModule", etm.Name); + + TestScene scene = new SceneHelpers().SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + SceneHelpers.SetupSceneModules(scene, config, etm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + sp.AbsolutePosition = new Vector3(30, 31, 32); + scene.RequestTeleportLocation( + sp.ControllingClient, + scene.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + Assert.That(sp.AbsolutePosition, Is.EqualTo(teleportPosition)); + + Assert.That(scene.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(scene.GetChildAgentCount(), Is.EqualTo(0)); + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + } + +/* + [Test] + public void TestSameSimulatorIsolatedRegionsV1() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + // FIXME: Hack - this is here temporarily to revert back to older entity transfer behaviour + lscm.ServiceVersion = 0.1f; + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = new Vector3(30, 31, 32); + + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate( + (TestClient)sp.ControllingClient, destinationTestClients); + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // SetupInformClientOfNeighbour() will have handled the callback into the target scene to setup the child + // agent. This call will now complete the movement of the user into the destination and upgrade the agent + // from child to root. + destinationTestClients[0].CompleteMovement(); + + Assert.That(sceneA.GetScenePresence(userId), Is.Null); + + ScenePresence sceneBSp = sceneB.GetScenePresence(userId); + Assert.That(sceneBSp, Is.Not.Null); + Assert.That(sceneBSp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneB.RegionInfo.RegionName)); + Assert.That(sceneBSp.AbsolutePosition, Is.EqualTo(teleportPosition)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + } +*/ + + [Test] + public void TestSameSimulatorIsolatedRegionsV2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = new Vector3(30, 31, 32); + + sceneA.Update(4); + sceneB.Update(4); + + List destinationTestClients = new List(); + EntityTransferHelpers.SetupSendRegionTeleportTriggersDestinationClientCreateAndCompleteMovement( + (TestClient)sp.ControllingClient, destinationTestClients); + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // Assert.That(sceneA.GetScenePresence(userId), Is.Null); + sceneA.Update(4); + sceneB.Update(4); + + ScenePresence sceneBSp = sceneB.GetScenePresence(userId); + Assert.That(sceneBSp, Is.Not.Null); + Assert.That(sceneBSp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneB.RegionInfo.RegionName)); + Assert.That(sceneBSp.AbsolutePosition.X, Is.EqualTo(teleportPosition.X)); + Assert.That(sceneBSp.AbsolutePosition.Y, Is.EqualTo(teleportPosition.Y)); + + //Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(0)); + //Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + //Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(1)); + //Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). + // Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + } + + /// + /// Test teleport procedures when the target simulator returns false when queried about access. + /// + [Test] + public void TestSameSimulatorIsolatedRegions_DeniedOnQueryAccess() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + Vector3 preTeleportPosition = new Vector3(30, 31, 32); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("EntityTransferModule", etmA.Name); + config.Configs["Modules"].Set("SimulationServices", lscm.Name); + + config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + config.Configs["EntityTransfer"].Set("wait_for_callback", false); + + config.AddConfig("Startup"); + config.Configs["Startup"].Set("serverside_object_permissions", true); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA ); + + // We need to set up the permisions module on scene B so that our later use of agent limit to deny + // QueryAccess won't succeed anyway because administrators are always allowed in and the default + // IsAdministrator if no permissions module is present is true. + SceneHelpers.SetupSceneModules(sceneB, config, new object[] { new DefaultPermissionsModule(), etmB }); + + // Shared scene modules + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = preTeleportPosition; + + // Make sceneB return false on query access + sceneB.RegionInfo.RegionSettings.AgentLimit = 0; + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + +// ((TestClient)sp.ControllingClient).CompleteTeleportClientSide(); + + Assert.That(sceneB.GetScenePresence(userId), Is.Null); + + ScenePresence sceneASp = sceneA.GetScenePresence(userId); + Assert.That(sceneASp, Is.Not.Null); + Assert.That(sceneASp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneA.RegionInfo.RegionName)); + Assert.That(sceneASp.AbsolutePosition.X, Is.EqualTo(preTeleportPosition.X)); + Assert.That(sceneASp.AbsolutePosition.Y, Is.EqualTo(preTeleportPosition.Y)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } + + /// + /// Test teleport procedures when the target simulator create agent step is refused. + /// + [Test] + public void TestSameSimulatorIsolatedRegions_DeniedOnCreateAgent() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + Vector3 preTeleportPosition = new Vector3(30, 31, 32); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("EntityTransferModule", etmA.Name); + config.Configs["Modules"].Set("SimulationServices", lscm.Name); + + config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + config.Configs["EntityTransfer"].Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA); + SceneHelpers.SetupSceneModules(sceneB, config, etmB); + + // Shared scene modules + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = preTeleportPosition; + + sceneA.Update(4); + sceneB.Update(4); + + // Make sceneB refuse CreateAgent + sceneB.LoginsEnabled = false; + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // ((TestClient)sp.ControllingClient).CompleteTeleportClientSide(); + + sceneA.Update(4); + sceneB.Update(4); + + Assert.That(sceneB.GetScenePresence(userId), Is.Null); + + ScenePresence sceneASp = sceneA.GetScenePresence(userId); + Assert.That(sceneASp, Is.Not.Null); + Assert.That(sceneASp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneA.RegionInfo.RegionName)); + Assert.That(sceneASp.AbsolutePosition.X, Is.EqualTo(preTeleportPosition.X)); + Assert.That(sceneASp.AbsolutePosition.Y, Is.EqualTo(preTeleportPosition.Y)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } + + /// + /// Test teleport when the destination region does not process (or does not receive) the connection attempt + /// from the viewer. + /// + /// + /// This could be quite a common case where the source region can connect to a remove destination region + /// (for CreateAgent) but the viewer cannot reach the destination region due to network issues. + /// + [Test] + public void TestSameSimulatorIsolatedRegions_DestinationDidNotProcessViewerConnection() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + Vector3 preTeleportPosition = new Vector3(30, 31, 32); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("EntityTransferModule", etmA.Name); + config.Configs["Modules"].Set("SimulationServices", lscm.Name); + + config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + config.Configs["EntityTransfer"].Set("wait_for_callback", false); + +// config.AddConfig("Startup"); +// config.Configs["Startup"].Set("serverside_object_permissions", true); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1002, 1000); + + SceneHelpers.SetupSceneModules(sceneA, config, etmA ); + + // We need to set up the permisions module on scene B so that our later use of agent limit to deny + // QueryAccess won't succeed anyway because administrators are always allowed in and the default + // IsAdministrator if no permissions module is present is true. + SceneHelpers.SetupSceneModules(sceneB, config, new object[] { new DefaultPermissionsModule(), etmB }); + + // Shared scene modules + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + ScenePresence sp = SceneHelpers.AddScenePresence(sceneA, userId); + sp.AbsolutePosition = preTeleportPosition; + + sceneA.Update(4); + sceneB.Update(4); + + sceneA.RequestTeleportLocation( + sp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + // FIXME: Not setting up InformClientOfNeighbour on the TestClient means that it does not initiate + // communication with the destination region. But this is a very non-obvious way of doing it - really we + // should be forced to expicitly set this up. + sceneA.Update(4); + sceneB.Update(4); + + Assert.That(sceneB.GetScenePresence(userId), Is.Null); + + ScenePresence sceneASp = sceneA.GetScenePresence(userId); + Assert.That(sceneASp, Is.Not.Null); + Assert.That(sceneASp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneA.RegionInfo.RegionName)); + Assert.That(sceneASp.AbsolutePosition.X, Is.EqualTo(preTeleportPosition.X)); + Assert.That(sceneASp.AbsolutePosition.Y, Is.EqualTo(preTeleportPosition.Y)); + + sceneA.SceneGraph.RecalculateStats(); + sceneB.SceneGraph.RecalculateStats(); + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } + +/* + [Test] + public void TestSameSimulatorNeighbouringRegionsV1() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + IConfig entityTransferConfig = config.AddConfig("EntityTransfer"); + + // In order to run a single threaded regression test we do not want the entity transfer module waiting + // for a callback from the destination scene before removing its avatar data. + entityTransferConfig.Set("wait_for_callback", false); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1001, 1000); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + // FIXME: Hack - this is here temporarily to revert back to older entity transfer behaviour + lscm.ServiceVersion = 0.1f; + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeSceneASp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeSceneASp.AbsolutePosition = new Vector3(30, 31, 32); + + Assert.That(beforeSceneASp, Is.Not.Null); + Assert.That(beforeSceneASp.IsChildAgent, Is.False); + + ScenePresence beforeSceneBSp = sceneB.GetScenePresence(userId); + Assert.That(beforeSceneBSp, Is.Not.Null); + Assert.That(beforeSceneBSp.IsChildAgent, Is.True); + + // In this case, we will not receieve a second InformClientOfNeighbour since the viewer already knows + // about the neighbour region it is teleporting to. + sceneA.RequestTeleportLocation( + beforeSceneASp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + destinationTestClients[0].CompleteMovement(); + + ScenePresence afterSceneASp = sceneA.GetScenePresence(userId); + Assert.That(afterSceneASp, Is.Not.Null); + Assert.That(afterSceneASp.IsChildAgent, Is.True); + + ScenePresence afterSceneBSp = sceneB.GetScenePresence(userId); + Assert.That(afterSceneBSp, Is.Not.Null); + Assert.That(afterSceneBSp.IsChildAgent, Is.False); + Assert.That(afterSceneBSp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneB.RegionInfo.RegionName)); + Assert.That(afterSceneBSp.AbsolutePosition, Is.EqualTo(teleportPosition)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } +*/ + + [Test] + public void TestSameSimulatorNeighbouringRegionsV2() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + EntityTransferModule etmA = new EntityTransferModule(); + EntityTransferModule etmB = new EntityTransferModule(); + LocalSimulationConnectorModule lscm = new LocalSimulationConnectorModule(); + + IConfigSource config = new IniConfigSource(); + IConfig modulesConfig = config.AddConfig("Modules"); + modulesConfig.Set("EntityTransferModule", etmA.Name); + modulesConfig.Set("SimulationServices", lscm.Name); + + SceneHelpers sh = new SceneHelpers(); + TestScene sceneA = sh.SetupScene("sceneA", TestHelpers.ParseTail(0x100), 1000, 1000); + TestScene sceneB = sh.SetupScene("sceneB", TestHelpers.ParseTail(0x200), 1001, 1000); + + SceneHelpers.SetupSceneModules(new Scene[] { sceneA, sceneB }, config, lscm); + SceneHelpers.SetupSceneModules(sceneA, config, new CapabilitiesModule(), etmA); + SceneHelpers.SetupSceneModules(sceneB, config, new CapabilitiesModule(), etmB); + + Vector3 teleportPosition = new Vector3(10, 11, 12); + Vector3 teleportLookAt = new Vector3(20, 21, 22); + + AgentCircuitData acd = SceneHelpers.GenerateAgentData(userId); + TestClient tc = new TestClient(acd, sceneA); + List destinationTestClients = new List(); + EntityTransferHelpers.SetupInformClientOfNeighbourTriggersNeighbourClientCreate(tc, destinationTestClients); + + ScenePresence beforeSceneASp = SceneHelpers.AddScenePresence(sceneA, tc, acd); + beforeSceneASp.AbsolutePosition = new Vector3(30, 31, 32); + + sceneA.Update(4); + sceneB.Update(4); + + Assert.That(beforeSceneASp, Is.Not.Null); + Assert.That(beforeSceneASp.IsChildAgent, Is.False); + + ScenePresence beforeSceneBSp = sceneB.GetScenePresence(userId); + Assert.That(beforeSceneBSp, Is.Not.Null); + Assert.That(beforeSceneBSp.IsChildAgent, Is.True); + + // Here, we need to make clientA's receipt of SendRegionTeleport trigger clientB's CompleteMovement(). This + // is to operate the teleport V2 mechanism where the EntityTransferModule will first request the client to + // CompleteMovement to the region and then call UpdateAgent to the destination region to confirm the receipt + // Both these operations will occur on different threads and will wait for each other. + // We have to do this via ThreadPool directly since FireAndForget has been switched to sync for the V1 + // test protocol, where we are trying to avoid unpredictable async operations in regression tests. + tc.OnTestClientSendRegionTeleport + += (regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL) + => ThreadPool.UnsafeQueueUserWorkItem(o => destinationTestClients[0].CompleteMovement(), null); + + sceneA.RequestTeleportLocation( + beforeSceneASp.ControllingClient, + sceneB.RegionInfo.RegionHandle, + teleportPosition, + teleportLookAt, + (uint)TeleportFlags.ViaLocation); + + sceneA.Update(4); + sceneB.Update(4); + + ScenePresence afterSceneASp = sceneA.GetScenePresence(userId); + Assert.That(afterSceneASp, Is.Not.Null); + Assert.That(afterSceneASp.IsChildAgent, Is.True); + + ScenePresence afterSceneBSp = sceneB.GetScenePresence(userId); + Assert.That(afterSceneBSp, Is.Not.Null); + Assert.That(afterSceneBSp.IsChildAgent, Is.False); + Assert.That(afterSceneBSp.Scene.RegionInfo.RegionName, Is.EqualTo(sceneB.RegionInfo.RegionName)); + Assert.That(afterSceneBSp.AbsolutePosition.X, Is.EqualTo(teleportPosition.X)); + Assert.That(afterSceneBSp.AbsolutePosition.Y, Is.EqualTo(teleportPosition.Y)); + + Assert.That(sceneA.GetRootAgentCount(), Is.EqualTo(0)); + Assert.That(sceneA.GetChildAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetRootAgentCount(), Is.EqualTo(1)); + Assert.That(sceneB.GetChildAgentCount(), Is.EqualTo(0)); + + // TODO: Add assertions to check correct circuit details in both scenes. + + // Lookat is sent to the client only - sp.Lookat does not yield the same thing (calculation from camera + // position instead). +// Assert.That(sp.Lookat, Is.EqualTo(teleportLookAt)); + +// TestHelpers.DisableLogging(); + } + } +} diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneStatisticsTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneStatisticsTests.cs new file mode 100644 index 00000000000..4ce6a95f363 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneStatisticsTests.cs @@ -0,0 +1,69 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class SceneStatisticsTests : OpenSimTestCase + { + private TestScene m_scene; + + [SetUp] + public void Init() + { + m_scene = new SceneHelpers().SetupScene(); + } + + [Test] + public void TestAddRemovePhysicalLinkset() + { + Assert.That(m_scene.SceneGraph.GetActiveObjectsCount(), Is.EqualTo(0)); + + UUID ownerId = TestHelpers.ParseTail(0x1); + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(3, ownerId, "so1", 0x10); + m_scene.AddSceneObject(so1); + so1.ScriptSetPhysicsStatus(true); + + Assert.That(m_scene.SceneGraph.GetTotalObjectsCount(), Is.EqualTo(3)); + Assert.That(m_scene.SceneGraph.GetActiveObjectsCount(), Is.EqualTo(3)); + + m_scene.DeleteSceneObject(so1, false); + + Assert.That(m_scene.SceneGraph.GetTotalObjectsCount(), Is.EqualTo(0)); + Assert.That(m_scene.SceneGraph.GetActiveObjectsCount(), Is.EqualTo(0)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTelehubTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTelehubTests.cs new file mode 100644 index 00000000000..2fccd968c23 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTelehubTests.cs @@ -0,0 +1,117 @@ +/* + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.World.Estate; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene telehub tests + /// + /// + /// TODO: Tests which run through normal functionality. Currently, the only test is one that checks behaviour + /// in the case of an error condition + /// + [TestFixture] + public class SceneTelehubTests : OpenSimTestCase + { + /// + /// Test for desired behaviour when a telehub has no spawn points + /// + [Test] + public void TestNoTelehubSpawnPoints() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + + /// + /// Test for desired behaviour when the scene object nominated as a telehub object does not exist. + /// + [Test] + public void TestNoTelehubSceneObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + EstateManagementModule emm = new EstateManagementModule(); + + SceneHelpers sh = new SceneHelpers(); + Scene scene = sh.SetupScene(); + SceneHelpers.SetupSceneModules(scene, emm); + + UUID telehubSceneObjectOwner = TestHelpers.ParseTail(0x1); + + SceneObjectGroup telehubSo = SceneHelpers.AddSceneObject(scene, "telehubObject", telehubSceneObjectOwner); + SceneObjectGroup spawnPointSo = SceneHelpers.AddSceneObject(scene, "spawnpointObject", telehubSceneObjectOwner); + + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "connect", telehubSo.LocalId); + emm.HandleOnEstateManageTelehub(null, UUID.Zero, UUID.Zero, "spawnpoint add", spawnPointSo.LocalId); + scene.RegionInfo.EstateSettings.AllowDirectTeleport = false; + + scene.DeleteSceneObject(telehubSo, false); + + // Must still be possible to successfully log in + UUID loggingInUserId = TestHelpers.ParseTail(0x2); + + UserAccount ua + = UserAccountHelpers.CreateUserWithInventory(scene, "Test", "User", loggingInUserId, "password"); + + SceneHelpers.AddScenePresence(scene, ua); + + Assert.That(scene.GetScenePresence(loggingInUserId), Is.Not.Null); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTests.cs new file mode 100644 index 00000000000..225199d875a --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SceneTests.cs @@ -0,0 +1,106 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using System.Threading; +using System.Timers; +using Timer=System.Timers.Timer; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.CoreModules.World.Serialiser; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Simulation; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + /// + /// Scene presence tests + /// + [TestFixture] + public class SceneTests : OpenSimTestCase + { + [Test] + public void TestCreateScene() + { + TestHelpers.InMethod(); + + new SceneHelpers().SetupScene(); + } + + [Test] + public void TestCreateVarScene() + { + TestHelpers.InMethod(); + UUID regionUuid = TestHelpers.ParseTail(0x1); + uint sizeX = 512; + uint sizeY = 512; + + Scene scene + = new SceneHelpers().SetupScene("scene", regionUuid, 1000, 1000, sizeX, sizeY, new IniConfigSource()); + + Assert.AreEqual(sizeX, scene.RegionInfo.RegionSizeX); + Assert.AreEqual(sizeY, scene.RegionInfo.RegionSizeY); + } + + /// + /// Very basic scene update test. Should become more elaborate with time. + /// + [Test] + public void TestUpdateScene() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + scene.Update(1); + + Assert.That(scene.Frame, Is.EqualTo(1)); + } + + [Test] + public void TestShutdownScene() + { + TestHelpers.InMethod(); + + Scene scene = new SceneHelpers().SetupScene(); + scene.Close(); + + Assert.That(scene.ShuttingDown, Is.True); + Assert.That(scene.Active, Is.False); + + // Trying to update a shutdown scene should result in no update + scene.Update(1); + + Assert.That(scene.Frame, Is.EqualTo(0)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SharedRegionModuleTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SharedRegionModuleTests.cs new file mode 100644 index 00000000000..0ae26585543 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/SharedRegionModuleTests.cs @@ -0,0 +1,248 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Net; +using Mono.Addins; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim; +using OpenSim.ApplicationPlugins.RegionModulesController; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + public class SharedRegionModuleTests : OpenSimTestCase + { +// [Test] + public void TestLifecycle() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + UUID estateOwnerId = TestHelpers.ParseTail(0x1); + UUID regionId = TestHelpers.ParseTail(0x10); + + IConfigSource configSource = new IniConfigSource(); + configSource.AddConfig("Startup"); + configSource.AddConfig("Modules"); + +// // We use this to skip estate questions + // Turns out not to be needed is estate owner id is pre-set in region information. +// IConfig estateConfig = configSource.AddConfig(OpenSimBase.ESTATE_SECTION_NAME); +// estateConfig.Set("DefaultEstateOwnerName", "Zaphod Beeblebrox"); +// estateConfig.Set("DefaultEstateOwnerUUID", estateOwnerId); +// estateConfig.Set("DefaultEstateOwnerEMail", "zaphod@galaxy.com"); +// estateConfig.Set("DefaultEstateOwnerPassword", "two heads"); + + // For grid servic + configSource.AddConfig("GridService"); + configSource.Configs["Modules"].Set("GridServices", "RegionGridServicesConnector"); + configSource.Configs["GridService"].Set("StorageProvider", "OpenSim.Data.Null.dll:NullRegionData"); + configSource.Configs["GridService"].Set("LocalServiceModule", "OpenSim.Services.GridService.dll:GridService"); + configSource.Configs["GridService"].Set("ConnectionString", "!static"); + + RegionGridServicesConnector gridService = new RegionGridServicesConnector(); +// + OpenSim sim = new OpenSim(configSource); + + sim.SuppressExit = true; + sim.EnableInitialPluginLoad = false; + sim.LoadEstateDataService = false; + sim.NetServersInfo.HttpListenerPort = 0; + + IRegistryCore reg = sim.ApplicationRegistry; + + RegionInfo ri = new RegionInfo(); + ri.RegionID = regionId; + ri.EstateSettings.EstateOwner = estateOwnerId; + ri.InternalEndPoint = new IPEndPoint(0, 0); + + MockRegionModulesControllerPlugin rmcp = new MockRegionModulesControllerPlugin(); + sim.m_plugins = new List() { rmcp }; + reg.RegisterInterface(rmcp); + + // XXX: Have to initialize directly for now + rmcp.Initialise(sim); + + rmcp.AddNode(gridService); + + TestSharedRegion tsr = new TestSharedRegion(); + rmcp.AddNode(tsr); + + // FIXME: Want to use the real one eventually but this is currently directly tied into Mono.Addins + // which has been written in such a way that makes it impossible to use for regression tests. +// RegionModulesControllerPlugin rmcp = new RegionModulesControllerPlugin(); +// rmcp.LoadModulesFromAddins = false; +//// reg.RegisterInterface(rmcp); +// rmcp.Initialise(sim); +// rmcp.PostInitialise(); +// TypeExtensionNode node = new TypeExtensionNode(); +// node. +// rmcp.AddNode(node, configSource.Configs["Modules"], new Dictionary>()); + + sim.Startup(); + IScene scene; + sim.CreateRegion(ri, out scene); + + sim.Shutdown(); + + List co = tsr.CallOrder; + int expectedEventCount = 6; + + Assert.AreEqual( + expectedEventCount, + co.Count, + "Expected {0} events but only got {1} ({2})", + expectedEventCount, co.Count, string.Join(",", co)); + Assert.AreEqual("Initialise", co[0]); + Assert.AreEqual("PostInitialise", co[1]); + Assert.AreEqual("AddRegion", co[2]); + Assert.AreEqual("RegionLoaded", co[3]); + Assert.AreEqual("RemoveRegion", co[4]); + Assert.AreEqual("Close", co[5]); + } + } + + class TestSharedRegion : ISharedRegionModule + { + // FIXME: Should really use MethodInfo + public List CallOrder = new List(); + + public string Name { get { return "TestSharedRegion"; } } + + public Type ReplaceableInterface { get { return null; } } + + public void PostInitialise() + { + CallOrder.Add("PostInitialise"); + } + + public void Initialise(IConfigSource source) + { + CallOrder.Add("Initialise"); + } + + public void Close() + { + CallOrder.Add("Close"); + } + + public void AddRegion(Scene scene) + { + CallOrder.Add("AddRegion"); + } + + public void RemoveRegion(Scene scene) + { + CallOrder.Add("RemoveRegion"); + } + + public void RegionLoaded(Scene scene) + { + CallOrder.Add("RegionLoaded"); + } + } + + class MockRegionModulesControllerPlugin : IRegionModulesController, IApplicationPlugin + { + // List of shared module instances, for adding to Scenes + private List m_sharedInstances = new List(); + + // Config access + private OpenSimBase m_openSim; + + public string Version { get { return "0"; } } + public string Name { get { return "MockRegionModulesControllerPlugin"; } } + + public void Initialise() {} + + public void Initialise(OpenSimBase sim) + { + m_openSim = sim; + } + + /// + /// Called when the application loading is completed + /// + public void PostInitialise() + { + foreach (ISharedRegionModule module in m_sharedInstances) + module.PostInitialise(); + } + + public void AddRegionToModules(Scene scene) + { + List sharedlist = new List(); + + foreach (ISharedRegionModule module in m_sharedInstances) + { + module.AddRegion(scene); + scene.AddRegionModule(module.Name, module); + + sharedlist.Add(module); + } + + foreach (ISharedRegionModule module in sharedlist) + { + module.RegionLoaded(scene); + } + } + + public void RemoveRegionFromModules(Scene scene) + { + foreach (IRegionModuleBase module in scene.RegionModules.Values) + { +// m_log.DebugFormat("[REGIONMODULE]: Removing scene {0} from module {1}", +// scene.RegionInfo.RegionName, module.Name); + module.RemoveRegion(scene); + } + + scene.RegionModules.Clear(); + } + + public void AddNode(ISharedRegionModule module) + { + m_sharedInstances.Add(module); + module.Initialise(m_openSim.ConfigSource.Source); + } + + public void Dispose() + { + // We expect that all regions have been removed already + while (m_sharedInstances.Count > 0) + { + m_sharedInstances[0].Close(); + m_sharedInstances.RemoveAt(0); + } + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/TaskInventoryTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/TaskInventoryTests.cs new file mode 100644 index 00000000000..7d4d703cffa --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/TaskInventoryTests.cs @@ -0,0 +1,163 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Tests +{ + [TestFixture] + public class TaskInventoryTests : OpenSimTestCase + { + [Test] + public void TestAddTaskInventoryItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene); + SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(1, user1.PrincipalID); + SceneObjectPart sop1 = sog1.RootPart; + + // Create an object embedded inside the first + UUID taskSceneObjectItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); + TaskInventoryHelpers.AddSceneObject(scene.AssetService, sop1, "tso", taskSceneObjectItemId, user1.PrincipalID); + + TaskInventoryItem addedItem = sop1.Inventory.GetInventoryItem(taskSceneObjectItemId); + Assert.That(addedItem.ItemID, Is.EqualTo(taskSceneObjectItemId)); + Assert.That(addedItem.OwnerID, Is.EqualTo(user1.PrincipalID)); + Assert.That(addedItem.ParentID, Is.EqualTo(sop1.UUID)); + Assert.That(addedItem.InvType, Is.EqualTo((int)InventoryType.Object)); + Assert.That(addedItem.Type, Is.EqualTo((int)AssetType.Object)); + } + + [Test] + public void TestRezObjectFromInventoryItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene); + SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(1, user1.PrincipalID); + SceneObjectPart sop1 = sog1.RootPart; + + // Create an object embedded inside the first + UUID taskSceneObjectItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); + TaskInventoryItem taskSceneObjectItem + = TaskInventoryHelpers.AddSceneObject(scene.AssetService, sop1, "tso", taskSceneObjectItemId, user1.PrincipalID); + + scene.AddSceneObject(sog1); + + Vector3 rezPos = new Vector3(10, 10, 10); + Quaternion rezRot = new Quaternion(0.5f, 0.5f, 0.5f, 0.5f); + Vector3 rezVel = new Vector3(2, 2, 2); + + scene.RezObject(sop1, taskSceneObjectItem, rezPos, rezRot, rezVel, 0,false); + + SceneObjectGroup rezzedObject = scene.GetSceneObjectGroup("tso"); + + Assert.That(rezzedObject, Is.Not.Null); + Assert.That(rezzedObject.AbsolutePosition, Is.EqualTo(rezPos)); + + // Velocity doesn't get applied, probably because there is no physics in tests (yet) + //Assert.That(rezzedObject.Velocity, Is.EqualTo(rezVel)); + Assert.That(rezzedObject.Velocity, Is.EqualTo(Vector3.Zero)); + + // Confusingly, this isn't the rezzedObject.Rotation + Assert.That(rezzedObject.RootPart.RotationOffset, Is.EqualTo(rezRot)); + } + + /// + /// Test MoveTaskInventoryItem from a part inventory to a user inventory where the item has no parent folder assigned. + /// + /// + /// This should place it in the most suitable user folder. + /// + [Test] + public void TestMoveTaskInventoryItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene); + SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(1, user1.PrincipalID); + SceneObjectPart sop1 = sog1.RootPart; + TaskInventoryItem sopItem1 + = TaskInventoryHelpers.AddNotecard( + scene.AssetService, sop1, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); + + InventoryFolderBase folder + = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, user1.PrincipalID, "Objects")[0]; + + // Perform test + string message; + scene.MoveTaskInventoryItem(user1.PrincipalID, folder.ID, sop1, sopItem1.ItemID, out message); + + InventoryItemBase ncUserItem + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, user1.PrincipalID, "Objects/ncItem"); + Assert.That(ncUserItem, Is.Not.Null, "Objects/ncItem was not found"); + } + + /// + /// Test MoveTaskInventoryItem from a part inventory to a user inventory where the item has no parent folder assigned. + /// + /// + /// This should place it in the most suitable user folder. + /// + [Test] + public void TestMoveTaskInventoryItemNoParent() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene); + SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(1, user1.PrincipalID); + + SceneObjectPart sop1 = sog1.RootPart; + TaskInventoryItem sopItem1 + = TaskInventoryHelpers.AddNotecard( + scene.AssetService, sop1, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); + + // Perform test + string message; + scene.MoveTaskInventoryItem(user1.PrincipalID, UUID.Zero, sop1, sopItem1.ItemID, out message); + + InventoryItemBase ncUserItem + = InventoryArchiveUtils.FindItemByPath(scene.InventoryService, user1.PrincipalID, "Notecards/ncItem"); + Assert.That(ncUserItem, Is.Not.Null, "Notecards/ncItem was not found"); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UserInventoryTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UserInventoryTests.cs new file mode 100644 index 00000000000..2c53450cecc --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UserInventoryTests.cs @@ -0,0 +1,191 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Tests +{ + [TestFixture] + public class UserInventoryTests : OpenSimTestCase + { + [Test] + public void TestCreateInventoryFolders() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // For this test both folders will have the same name which is legal in SL user inventories. + string foldersName = "f1"; + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1001)); + + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, foldersName, false); + + List oneFolder + = UserInventoryHelpers.GetInventoryFolders(scene.InventoryService, user1.PrincipalID, foldersName); + + Assert.That(oneFolder.Count, Is.EqualTo(1)); + InventoryFolderBase firstRetrievedFolder = oneFolder[0]; + Assert.That(firstRetrievedFolder.Name, Is.EqualTo(foldersName)); + + UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, foldersName, false); + + List twoFolders + = UserInventoryHelpers.GetInventoryFolders(scene.InventoryService, user1.PrincipalID, foldersName); + + Assert.That(twoFolders.Count, Is.EqualTo(2)); + Assert.That(twoFolders[0].Name, Is.EqualTo(foldersName)); + Assert.That(twoFolders[1].Name, Is.EqualTo(foldersName)); + Assert.That(twoFolders[0].ID, Is.Not.EqualTo(twoFolders[1].ID)); + } + + [Test] + public void TestGiveInventoryItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1001)); + UserAccount user2 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1002)); + InventoryItemBase item1 = UserInventoryHelpers.CreateInventoryItem(scene, "item1", user1.PrincipalID); + + string message; + + scene.GiveInventoryItem(user2.PrincipalID, user1.PrincipalID, item1.ID, out message); + + InventoryItemBase retrievedItem1 + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, user2.PrincipalID, "Notecards/item1"); + + Assert.That(retrievedItem1, Is.Not.Null); + + // Try giving back the freshly received item + scene.GiveInventoryItem(user1.PrincipalID, user2.PrincipalID, retrievedItem1.ID, out message); + + List reretrievedItems + = UserInventoryHelpers.GetInventoryItems(scene.InventoryService, user1.PrincipalID, "Notecards/item1"); + + Assert.That(reretrievedItems.Count, Is.EqualTo(2)); + } + + [Test] + public void TestGiveInventoryFolder() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + Scene scene = new SceneHelpers().SetupScene(); + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1001)); + UserAccount user2 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1002)); + InventoryFolderBase folder1 + = UserInventoryHelpers.CreateInventoryFolder(scene.InventoryService, user1.PrincipalID, "folder1", false); + + scene.GiveInventoryFolder(null, user2.PrincipalID, user1.PrincipalID, folder1.ID, UUID.Zero); + + InventoryFolderBase retrievedFolder1 + = UserInventoryHelpers.GetInventoryFolder(scene.InventoryService, user2.PrincipalID, "folder1"); + + Assert.That(retrievedFolder1, Is.Not.Null); + + // Try giving back the freshly received folder + scene.GiveInventoryFolder(null, user1.PrincipalID, user2.PrincipalID, retrievedFolder1.ID, UUID.Zero); + + List reretrievedFolders + = UserInventoryHelpers.GetInventoryFolders(scene.InventoryService, user1.PrincipalID, "folder1"); + + Assert.That(reretrievedFolders.Count, Is.EqualTo(2)); + } + + // Work in Progress test. All Assertions pertaining permissions are commented for now. + [Test] + public void TestGiveInventoryItemFullPerms() + { + TestHelpers.InMethod(); + + List modules = new List(); + IConfigSource config = DefaultConfig(modules); + Scene scene = new SceneHelpers().SetupScene("Inventory Permissions", UUID.Random(), 1000, 1000, config); + SceneHelpers.SetupSceneModules(scene, config, modules.ToArray()); + + UserAccount user1 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1001)); + UserAccount user2 = UserAccountHelpers.CreateUserWithInventory(scene, TestHelpers.ParseTail(1002)); + ScenePresence sp1 = SceneHelpers.AddScenePresence(scene, user1.PrincipalID); + ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, user2.PrincipalID); + + InventoryItemBase item1 = UserInventoryHelpers.CreateInventoryItem(scene, "SomeObject", user1.PrincipalID, InventoryType.Object); + // Set All perms in inventory + item1.NextPermissions = (uint)OpenMetaverse.PermissionMask.All; + scene.UpdateInventoryItem(sp1.ControllingClient, UUID.Zero, item1.ID, item1); + //Assert.That((item1.NextPermissions & (uint)OpenMetaverse.PermissionMask.All) == (uint)OpenMetaverse.PermissionMask.All); + + string message; + + InventoryItemBase retrievedItem1 = scene.GiveInventoryItem(user2.PrincipalID, user1.PrincipalID, item1.ID, out message); + Assert.That(retrievedItem1, Is.Not.Null); + //Assert.That((retrievedItem1.CurrentPermissions & (uint)OpenMetaverse.PermissionMask.All) == (uint)OpenMetaverse.PermissionMask.All); + + retrievedItem1 + = UserInventoryHelpers.GetInventoryItem(scene.InventoryService, user2.PrincipalID, "Objects/SomeObject"); + Assert.That(retrievedItem1, Is.Not.Null); + //Assert.That((retrievedItem1.BasePermissions & (uint)OpenMetaverse.PermissionMask.All) == (uint)OpenMetaverse.PermissionMask.All); + //Assert.That((retrievedItem1.CurrentPermissions & (uint)OpenMetaverse.PermissionMask.All) == (uint)OpenMetaverse.PermissionMask.All); + + // Rez the object + scene.RezObject(sp2.ControllingClient, retrievedItem1.ID, UUID.Zero, Vector3.Zero, Vector3.Zero, UUID.Zero, 0, false, false, false, UUID.Zero); + SceneObjectGroup sog = scene.GetSceneObjectGroup("SomeObject"); + Assert.That(sog, Is.Not.Null); + + // This is failing for all sorts of reasons. We'll fix it after perms are fixed. + //Console.WriteLine("Item Perms " + retrievedItem1.CurrentPermissions + " Obj Owner Perms " + sog.RootPart.OwnerMask + " Base Perms " + sog.RootPart.BaseMask + "\n"); + //Assert.True((sog.RootPart.OwnerMask & (uint)OpenMetaverse.PermissionMask.All) == (uint)OpenMetaverse.PermissionMask.All); + + } + + public static IConfigSource DefaultConfig(List modules) + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + config.AddConfig("Permissions"); + config.Configs["Permissions"].Set("permissionmodules", "DefaultPermissionsModule"); + config.Configs["Permissions"].Set("serverside_object_permissions", true); + config.Configs["Permissions"].Set("propagate_permissions", true); + + modules.Add(new BasicInventoryAccessModule()); + return config; + } + + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UuidGathererTests.cs b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UuidGathererTests.cs new file mode 100644 index 00000000000..efe71e9dcf0 --- /dev/null +++ b/Tests/OpenSim.Region.Framework.Tests/Scenes/Tests/UuidGathererTests.cs @@ -0,0 +1,160 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using System.Text; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.Framework.Scenes.Tests +{ + [TestFixture] + public class UuidGathererTests : OpenSimTestCase + { + protected IAssetService m_assetService; + protected UuidGatherer m_uuidGatherer; + + protected static string noteBase = @"Linden text version 2\n{\nLLEmbeddedItems version 1\n +{\ncount 0\n}\nText length xxx\n"; // len does not matter on this test + [SetUp] + public void Init() + { + // FIXME: We don't need a full scene here - it would be enough to set up the asset service. + Scene scene = new SceneHelpers().SetupScene(); + m_assetService = scene.AssetService; + m_uuidGatherer = new UuidGatherer(m_assetService); + } + + [Test] + public void TestCorruptAsset() + { + TestHelpers.InMethod(); + + UUID corruptAssetUuid = UUID.Parse("00000000-0000-0000-0000-000000000666"); + AssetBase corruptAsset + = AssetHelpers.CreateAsset(corruptAssetUuid, AssetType.Notecard, noteBase + "CORRUPT ASSET", UUID.Zero); + m_assetService.Store(corruptAsset); + + m_uuidGatherer.AddForInspection(corruptAssetUuid); + m_uuidGatherer.GatherAll(); + + // We count the uuid as gathered even if the asset itself is corrupt. + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(1)); + } + + /// + /// Test requests made for non-existent assets while we're gathering + /// + [Test] + public void TestMissingAsset() + { + TestHelpers.InMethod(); + + UUID missingAssetUuid = UUID.Parse("00000000-0000-0000-0000-000000000666"); + + m_uuidGatherer.AddForInspection(missingAssetUuid); + m_uuidGatherer.GatherAll(); + + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(0)); + } + + [Test] + public void TestNotecardAsset() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + UUID ownerId = TestHelpers.ParseTail(0x10); + UUID embeddedId = TestHelpers.ParseTail(0x20); + UUID secondLevelEmbeddedId = TestHelpers.ParseTail(0x21); + UUID missingEmbeddedId = TestHelpers.ParseTail(0x22); + UUID ncAssetId = TestHelpers.ParseTail(0x30); + + AssetBase ncAsset + = AssetHelpers.CreateNotecardAsset( + ncAssetId, string.Format("{0}Hello{1}World{2}", noteBase, embeddedId, missingEmbeddedId)); + m_assetService.Store(ncAsset); + + AssetBase embeddedAsset + = AssetHelpers.CreateNotecardAsset(embeddedId, string.Format("{0}{1} We'll meet again.", noteBase, secondLevelEmbeddedId)); + m_assetService.Store(embeddedAsset); + + AssetBase secondLevelEmbeddedAsset + = AssetHelpers.CreateNotecardAsset(secondLevelEmbeddedId, noteBase + "Don't know where, don't know when."); + m_assetService.Store(secondLevelEmbeddedAsset); + + m_uuidGatherer.AddForInspection(ncAssetId); + m_uuidGatherer.GatherAll(); + + // foreach (UUID key in m_uuidGatherer.GatheredUuids.Keys) + // System.Console.WriteLine("key : {0}", key); + + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(3)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(ncAssetId)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(embeddedId)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(secondLevelEmbeddedId)); + } + + [Test] + public void TestTaskItems() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + UUID ownerId = TestHelpers.ParseTail(0x10); + + SceneObjectGroup soL0 = SceneHelpers.CreateSceneObject(1, ownerId, "l0", 0x20); + SceneObjectGroup soL1 = SceneHelpers.CreateSceneObject(1, ownerId, "l1", 0x21); + SceneObjectGroup soL2 = SceneHelpers.CreateSceneObject(1, ownerId, "l2", 0x22); + + TaskInventoryHelpers.AddScript( + m_assetService, soL2.RootPart, TestHelpers.ParseTail(0x33), TestHelpers.ParseTail(0x43), "l3-script", "gibberish"); + + TaskInventoryHelpers.AddSceneObject( + m_assetService, soL1.RootPart, "l2-item", TestHelpers.ParseTail(0x32), soL2, TestHelpers.ParseTail(0x42)); + TaskInventoryHelpers.AddSceneObject( + m_assetService, soL0.RootPart, "l1-item", TestHelpers.ParseTail(0x31), soL1, TestHelpers.ParseTail(0x41)); + + m_uuidGatherer.AddForInspection(soL0); + m_uuidGatherer.GatherAll(); + +// foreach (UUID key in m_uuidGatherer.GatheredUuids.Keys) +// System.Console.WriteLine("key : {0}", key); + + // We expect to see the default prim texture and the assets of the contained task items + Assert.That(m_uuidGatherer.GatheredUuids.Count, Is.EqualTo(4)); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(new UUID(Constants.DefaultTexture))); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(TestHelpers.ParseTail(0x41))); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(TestHelpers.ParseTail(0x42))); + Assert.That(m_uuidGatherer.GatheredUuids.ContainsKey(TestHelpers.ParseTail(0x43))); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.OptionalModules.Tests/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs b/Tests/OpenSim.Region.OptionalModules.Tests/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs new file mode 100644 index 00000000000..ccfcd8b3540 --- /dev/null +++ b/Tests/OpenSim.Region.OptionalModules.Tests/Avatar/XmlRpcGroups/Tests/GroupsModuleTests.cs @@ -0,0 +1,276 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Net; +using System.Reflection; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Messages.Linden; +using OpenMetaverse.Packets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.ClientStack.Linden; +using OpenSim.Region.CoreModules.Avatar.InstantMessage; +using OpenSim.Region.CoreModules.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups.Tests +{ + /// + /// Basic groups module tests + /// + [TestFixture] + public class GroupsModuleTests : OpenSimTestCase + { + [SetUp] + public override void SetUp() + { + base.SetUp(); + + uint port = 9999; + uint sslPort = 9998; + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + MainServer.RemoveHttpServer(port); + + BaseHttpServer server = new BaseHttpServer(port, false, sslPort, ""); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + } + + [Test] + public void TestSendAgentGroupDataUpdate() + { +/* AgentGroupDataUpdate is udp + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + IConfigSource configSource = new IniConfigSource(); + IConfig config = configSource.AddConfig("Groups"); + config.Set("Enabled", true); + config.Set("Module", "GroupsModule"); + config.Set("DebugEnabled", true); + + GroupsModule gm = new GroupsModule(); + EventQueueGetModule eqgm = new EventQueueGetModule(); + + // We need a capabilities module active so that adding the scene presence creates an event queue in the + // EventQueueGetModule + SceneHelpers.SetupSceneModules( + scene, configSource, gm, new MockGroupsServicesConnector(), new CapabilitiesModule(), eqgm); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseStem("1")); + + gm.SendAgentGroupDataUpdate(sp.ControllingClient); + + Hashtable eventsResponse = eqgm.GetEvents(UUID.Zero, sp.UUID); + + if((int)eventsResponse["int_response_code"] != (int)HttpStatusCode.OK) + { + eventsResponse = eqgm.GetEvents(UUID.Zero, sp.UUID); + if((int)eventsResponse["int_response_code"] != (int)HttpStatusCode.OK) + eventsResponse = eqgm.GetEvents(UUID.Zero, sp.UUID); + } + + Assert.That((int)eventsResponse["int_response_code"], Is.EqualTo((int)HttpStatusCode.OK)); + +// Console.WriteLine("Response [{0}]", (string)eventsResponse["str_response_string"]); + + OSDMap rawOsd = (OSDMap)OSDParser.DeserializeLLSDXml((string)eventsResponse["str_response_string"]); + OSDArray eventsOsd = (OSDArray)rawOsd["events"]; + + bool foundUpdate = false; + foreach (OSD osd in eventsOsd) + { + OSDMap eventOsd = (OSDMap)osd; + + if (eventOsd["message"] == "AgentGroupDataUpdate") + foundUpdate = true; + } + + Assert.That(foundUpdate, Is.True, "Did not find AgentGroupDataUpdate in response"); + + // TODO: More checking of more actual event data. +*/ + } + + [Test] + public void TestSendGroupNotice() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + + MessageTransferModule mtm = new MessageTransferModule(); + GroupsModule gm = new GroupsModule(); + GroupsMessagingModule gmm = new GroupsMessagingModule(); + MockGroupsServicesConnector mgsc = new MockGroupsServicesConnector(); + + IConfigSource configSource = new IniConfigSource(); + + { + IConfig config = configSource.AddConfig("Messaging"); + config.Set("MessageTransferModule", mtm.Name); + } + + { + IConfig config = configSource.AddConfig("Groups"); + config.Set("Enabled", true); + config.Set("Module", gm.Name); + config.Set("DebugEnabled", true); + config.Set("MessagingModule", gmm.Name); + config.Set("MessagingEnabled", true); + } + + SceneHelpers.SetupSceneModules(scene, configSource, mgsc, mtm, gm, gmm); + + UUID userId = TestHelpers.ParseTail(0x1); + string subjectText = "newman"; + string messageText = "Hello"; + string combinedSubjectMessage = string.Format("{0}|{1}", subjectText, messageText); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + TestClient tc = (TestClient)sp.ControllingClient; + + UUID groupID = gm.CreateGroup(tc, "group1", null, true, UUID.Zero, 0, true, true, true); + gm.JoinGroupRequest(tc, groupID); + + // Create a second user who doesn't want to receive notices + ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x2)); + TestClient tc2 = (TestClient)sp2.ControllingClient; + gm.JoinGroupRequest(tc2, groupID); + gm.SetGroupAcceptNotices(tc2, groupID, false, true); + + List spReceivedMessages = new List(); + tc.OnReceivedInstantMessage += im => spReceivedMessages.Add(im); + + List sp2ReceivedMessages = new List(); + tc2.OnReceivedInstantMessage += im => sp2ReceivedMessages.Add(im); + + GridInstantMessage noticeIm = new GridInstantMessage(); + noticeIm.fromAgentID = userId.Guid; + noticeIm.toAgentID = groupID.Guid; + noticeIm.message = combinedSubjectMessage; + noticeIm.dialog = (byte)InstantMessageDialog.GroupNotice; + + tc.HandleImprovedInstantMessage(noticeIm); + + Assert.That(spReceivedMessages.Count, Is.EqualTo(1)); + Assert.That(spReceivedMessages[0].message, Is.EqualTo(combinedSubjectMessage)); + + List notices = mgsc.GetGroupNotices(UUID.Zero, groupID); + Assert.AreEqual(1, notices.Count); + + // OpenSimulator (possibly also SL) transport the notice ID as the session ID! + Assert.AreEqual(notices[0].NoticeID.Guid, spReceivedMessages[0].imSessionID); + + Assert.That(sp2ReceivedMessages.Count, Is.EqualTo(0)); + } + + /// + /// Run test with the MessageOnlineUsersOnly flag set. + /// + [Test] + public void TestSendGroupNoticeOnlineOnly() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + TestScene scene = new SceneHelpers().SetupScene(); + + MessageTransferModule mtm = new MessageTransferModule(); + GroupsModule gm = new GroupsModule(); + GroupsMessagingModule gmm = new GroupsMessagingModule(); + + IConfigSource configSource = new IniConfigSource(); + + { + IConfig config = configSource.AddConfig("Messaging"); + config.Set("MessageTransferModule", mtm.Name); + } + + { + IConfig config = configSource.AddConfig("Groups"); + config.Set("Enabled", true); + config.Set("Module", gm.Name); + config.Set("DebugEnabled", true); + config.Set("MessagingModule", gmm.Name); + config.Set("MessagingEnabled", true); + config.Set("MessageOnlineUsersOnly", true); + } + + SceneHelpers.SetupSceneModules(scene, configSource, new MockGroupsServicesConnector(), mtm, gm, gmm); + + UUID userId = TestHelpers.ParseTail(0x1); + string subjectText = "newman"; + string messageText = "Hello"; + string combinedSubjectMessage = string.Format("{0}|{1}", subjectText, messageText); + + ScenePresence sp = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x1)); + TestClient tc = (TestClient)sp.ControllingClient; + + UUID groupID = gm.CreateGroup(tc, "group1", null, true, UUID.Zero, 0, true, true, true); + gm.JoinGroupRequest(tc, groupID); + + // Create a second user who doesn't want to receive notices + ScenePresence sp2 = SceneHelpers.AddScenePresence(scene, TestHelpers.ParseTail(0x2)); + TestClient tc2 = (TestClient)sp2.ControllingClient; + gm.JoinGroupRequest(tc2, groupID); + gm.SetGroupAcceptNotices(tc2, groupID, false, true); + + List spReceivedMessages = new List(); + tc.OnReceivedInstantMessage += im => spReceivedMessages.Add(im); + + List sp2ReceivedMessages = new List(); + tc2.OnReceivedInstantMessage += im => sp2ReceivedMessages.Add(im); + + GridInstantMessage noticeIm = new GridInstantMessage(); + noticeIm.fromAgentID = userId.Guid; + noticeIm.toAgentID = groupID.Guid; + noticeIm.message = combinedSubjectMessage; + noticeIm.dialog = (byte)InstantMessageDialog.GroupNotice; + + tc.HandleImprovedInstantMessage(noticeIm); + + Assert.That(spReceivedMessages.Count, Is.EqualTo(1)); + Assert.That(spReceivedMessages[0].message, Is.EqualTo(combinedSubjectMessage)); + + Assert.That(sp2ReceivedMessages.Count, Is.EqualTo(0)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.OptionalModules.Tests/Example/WebSocketEchoTest/WebSocketEchoModule.cs b/Tests/OpenSim.Region.OptionalModules.Tests/Example/WebSocketEchoTest/WebSocketEchoModule.cs new file mode 100644 index 00000000000..0747cc07cba --- /dev/null +++ b/Tests/OpenSim.Region.OptionalModules.Tests/Example/WebSocketEchoTest/WebSocketEchoModule.cs @@ -0,0 +1,175 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using OpenSim.Framework.Servers; +using Mono.Addins; +using log4net; +using Nini.Config; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +using OpenSim.Framework.Servers.HttpServer; + + +namespace OpenSim.Region.OptionalModules.WebSocketEchoModule +{ + + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "WebSocketEchoModule")] + public class WebSocketEchoModule : ISharedRegionModule + { + private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + private bool enabled; + public string Name { get { return "WebSocketEchoModule"; } } + + public Type ReplaceableInterface { get { return null; } } + + + private HashSet _activeHandlers = new HashSet(); + + public void Initialise(IConfigSource pConfig) + { + enabled = (pConfig.Configs["WebSocketEcho"] != null); +// if (enabled) +// m_log.DebugFormat("[WebSocketEchoModule]: INITIALIZED MODULE"); + } + + /// + /// This method sets up the callback to WebSocketHandlerCallback below when a HTTPRequest comes in for /echo + /// + public void PostInitialise() + { + if (enabled) + MainServer.Instance.AddWebSocketHandler("/echo", WebSocketHandlerCallback); + } + + // This gets called by BaseHttpServer and gives us an opportunity to set things on the WebSocket handler before we turn it on + public void WebSocketHandlerCallback(string path, WebSocketHttpServerHandler handler) + { + SubscribeToEvents(handler); + handler.SetChunksize(8192); + handler.NoDelay_TCP_Nagle = true; + handler.HandshakeAndUpgrade(); + } + + //These are our normal events + public void SubscribeToEvents(WebSocketHttpServerHandler handler) + { + handler.OnClose += HandlerOnOnClose; + handler.OnText += HandlerOnOnText; + handler.OnUpgradeCompleted += HandlerOnOnUpgradeCompleted; + handler.OnData += HandlerOnOnData; + handler.OnPong += HandlerOnOnPong; + } + + public void UnSubscribeToEvents(WebSocketHttpServerHandler handler) + { + handler.OnClose -= HandlerOnOnClose; + handler.OnText -= HandlerOnOnText; + handler.OnUpgradeCompleted -= HandlerOnOnUpgradeCompleted; + handler.OnData -= HandlerOnOnData; + handler.OnPong -= HandlerOnOnPong; + } + + private void HandlerOnOnPong(object sender, PongEventArgs pongdata) + { + m_log.Info("[WebSocketEchoModule]: Got a pong.. ping time: " + pongdata.PingResponseMS); + } + + private void HandlerOnOnData(object sender, WebsocketDataEventArgs data) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + obj.SendData(data.Data); + m_log.Info("[WebSocketEchoModule]: We received a bunch of ugly non-printable bytes"); + obj.SendPingCheck(); + } + + + private void HandlerOnOnUpgradeCompleted(object sender, UpgradeCompletedEventArgs completeddata) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + _activeHandlers.Add(obj); + } + + private void HandlerOnOnText(object sender, WebsocketTextEventArgs text) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + obj.SendMessage(text.Data); + m_log.Info("[WebSocketEchoModule]: We received this: " + text.Data); + } + + // Remove the references to our handler + private void HandlerOnOnClose(object sender, CloseEventArgs closedata) + { + WebSocketHttpServerHandler obj = sender as WebSocketHttpServerHandler; + UnSubscribeToEvents(obj); + + lock (_activeHandlers) + _activeHandlers.Remove(obj); + obj.Dispose(); + } + + // Shutting down.. so shut down all sockets. + // Note.. this should be done outside of an ienumerable if you're also hook to the close event. + public void Close() + { + if (!enabled) + return; + + // We convert this to a for loop so we're not in in an IEnumerable when the close + //call triggers an event which then removes item from _activeHandlers that we're enumerating + WebSocketHttpServerHandler[] items = new WebSocketHttpServerHandler[_activeHandlers.Count]; + _activeHandlers.CopyTo(items); + + for (int i = 0; i < items.Length; i++) + { + items[i].Close(string.Empty); + items[i].Dispose(); + } + _activeHandlers.Clear(); + MainServer.Instance.RemoveWebSocketHandler("/echo"); + } + + public void AddRegion(Scene scene) + { +// m_log.DebugFormat("[WebSocketEchoModule]: REGION {0} ADDED", scene.RegionInfo.RegionName); + } + + public void RemoveRegion(Scene scene) + { +// m_log.DebugFormat("[WebSocketEchoModule]: REGION {0} REMOVED", scene.RegionInfo.RegionName); + } + + public void RegionLoaded(Scene scene) + { +// m_log.DebugFormat("[WebSocketEchoModule]: REGION {0} LOADED", scene.RegionInfo.RegionName); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.OptionalModules.Tests/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs b/Tests/OpenSim.Region.OptionalModules.Tests/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs new file mode 100644 index 00000000000..77ee7855a52 --- /dev/null +++ b/Tests/OpenSim.Region.OptionalModules.Tests/Scripting/JsonStore/Tests/JsonStoreScriptModuleTests.cs @@ -0,0 +1,900 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using log4net; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Scripting.ScriptModuleComms; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.OptionalModules.Scripting.JsonStore.Tests +{ + /// + /// Tests for inventory functions in LSL + /// + [TestFixture] + public class JsonStoreScriptModuleTests : OpenSimTestCase + { + private Scene m_scene; + private MockScriptEngine m_engine; + private ScriptModuleCommsModule m_smcm; + private JsonStoreScriptModule m_jssm; + + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource configSource = new IniConfigSource(); + IConfig jsonStoreConfig = configSource.AddConfig("JsonStore"); + jsonStoreConfig.Set("Enabled", "true"); + + m_engine = new MockScriptEngine(); + m_smcm = new ScriptModuleCommsModule(); + JsonStoreModule jsm = new JsonStoreModule(); + m_jssm = new JsonStoreScriptModule(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, configSource, m_engine, m_smcm, jsm, m_jssm); + + try + { + m_smcm.RegisterScriptInvocation(this, "DummyTestMethod"); + } + catch (ArgumentException) + { + Assert.Ignore("Ignoring test since running on .NET 3.5 or earlier."); + } + + // XXX: Unfortunately, ICommsModule currently has no way of deregistering methods. + } + + private object InvokeOp(string name, params object[] args) + { + return InvokeOpOnHost(name, UUID.Zero, args); + } + + private object InvokeOpOnHost(string name, UUID hostId, params object[] args) + { + return m_smcm.InvokeOperation(hostId, UUID.Zero, name, args); + } + + [Test] + public void TestJsonCreateStore() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Test blank store + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + } + + // Test single element store + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + } + + // Test with an integer value + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 42.15 }"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Hello"); + Assert.That(value, Is.EqualTo("42.15")); + } + + // Test with an array as the root node + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "[ 'one', 'two', 'three' ]"); + Assert.That(storeId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "[1]"); + Assert.That(value, Is.EqualTo("two")); + } + } + + [Test] + public void TestJsonDestroyStore() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + int dsrv = (int)InvokeOp("JsonDestroyStore", storeId); + + Assert.That(dsrv, Is.EqualTo(1)); + + int tprv = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(tprv, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + } + + [Test] + public void TestJsonDestroyStoreNotExists() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + + int dsrv = (int)InvokeOp("JsonDestroyStore", fakeStoreId); + + Assert.That(dsrv, Is.EqualTo(0)); + } + + [Test] + public void TestJsonGetValue() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'Two' } }"); + + { + string value = (string)InvokeOp("JsonGetValue", storeId, "Hello.World"); + Assert.That(value, Is.EqualTo("Two")); + } + + // Test get of path section instead of leaf + { + string value = (string)InvokeOp("JsonGetValue", storeId, "Hello"); + Assert.That(value, Is.EqualTo("")); + } + + // Test get of non-existing value + { + string fakeValueGet = (string)InvokeOp("JsonGetValue", storeId, "foo"); + Assert.That(fakeValueGet, Is.EqualTo("")); + } + + // Test get from non-existing store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + string fakeStoreValueGet = (string)InvokeOp("JsonGetValue", fakeStoreId, "Hello"); + Assert.That(fakeStoreValueGet, Is.EqualTo("")); + } + } + + [Test] + public void TestJsonGetJson() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'Two' } }"); + + { + string value = (string)InvokeOp("JsonGetJson", storeId, "Hello.World"); + Assert.That(value, Is.EqualTo("'Two'")); + } + + // Test get of path section instead of leaf + { + string value = (string)InvokeOp("JsonGetJson", storeId, "Hello"); + Assert.That(value, Is.EqualTo("{\"World\":\"Two\"}")); + } + + // Test get of non-existing value + { + string fakeValueGet = (string)InvokeOp("JsonGetJson", storeId, "foo"); + Assert.That(fakeValueGet, Is.EqualTo("")); + } + + // Test get from non-existing store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + string fakeStoreValueGet = (string)InvokeOp("JsonGetJson", fakeStoreId, "Hello"); + Assert.That(fakeStoreValueGet, Is.EqualTo("")); + } + } + +// [Test] +// public void TestJsonTakeValue() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// UUID storeId +// = (UUID)m_smcm.InvokeOperation( +// UUID.Zero, UUID.Zero, "JsonCreateStore", new object[] { "{ 'Hello' : 'World' }" }); +// +// string value +// = (string)m_smcm.InvokeOperation( +// UUID.Zero, UUID.Zero, "JsonTakeValue", new object[] { storeId, "Hello" }); +// +// Assert.That(value, Is.EqualTo("World")); +// +// string value2 +// = (string)m_smcm.InvokeOperation( +// UUID.Zero, UUID.Zero, "JsonGetValue", new object[] { storeId, "Hello" }); +// +// Assert.That(value, Is.Null); +// } + + [Test] + public void TestJsonRemoveValue() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Test remove of node in object pointing to a string + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + + int returnValue = (int)InvokeOp( "JsonRemoveValue", storeId, "Hello"); + Assert.That(returnValue, Is.EqualTo(1)); + + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + + string returnValue2 = (string)InvokeOp("JsonGetValue", storeId, "Hello"); + Assert.That(returnValue2, Is.EqualTo("")); + } + + // Test remove of node in object pointing to another object + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'Wally' } }"); + + int returnValue = (int)InvokeOp( "JsonRemoveValue", storeId, "Hello"); + Assert.That(returnValue, Is.EqualTo(1)); + + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + + string returnValue2 = (string)InvokeOp("JsonGetJson", storeId, "Hello"); + Assert.That(returnValue2, Is.EqualTo("")); + } + + // Test remove of node in an array + { + UUID storeId + = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : [ 'value1', 'value2' ] }"); + + int returnValue = (int)InvokeOp( "JsonRemoveValue", storeId, "Hello[0]"); + Assert.That(returnValue, Is.EqualTo(1)); + + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello[0]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_VALUE)); + + result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello[1]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + + string stringReturnValue = (string)InvokeOp("JsonGetValue", storeId, "Hello[0]"); + Assert.That(stringReturnValue, Is.EqualTo("value2")); + + stringReturnValue = (string)InvokeOp("JsonGetJson", storeId, "Hello[1]"); + Assert.That(stringReturnValue, Is.EqualTo("")); + } + + // Test remove of non-existing value + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : 'World' }"); + + int fakeValueRemove = (int)InvokeOp("JsonRemoveValue", storeId, "Cheese"); + Assert.That(fakeValueRemove, Is.EqualTo(0)); + } + + { + // Test get from non-existing store + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int fakeStoreValueRemove = (int)InvokeOp("JsonRemoveValue", fakeStoreId, "Hello"); + Assert.That(fakeStoreValueRemove, Is.EqualTo(0)); + } + } + +// [Test] +// public void TestJsonTestPath() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'One' } }"); +// +// { +// int result = (int)InvokeOp("JsonTestPath", storeId, "Hello.World"); +// Assert.That(result, Is.EqualTo(1)); +// } +// +// // Test for path which does not resolve to a value. +// { +// int result = (int)InvokeOp("JsonTestPath", storeId, "Hello"); +// Assert.That(result, Is.EqualTo(0)); +// } +// +// { +// int result2 = (int)InvokeOp("JsonTestPath", storeId, "foo"); +// Assert.That(result2, Is.EqualTo(0)); +// } +// +// // Test with fake store +// { +// UUID fakeStoreId = TestHelpers.ParseTail(0x500); +// int fakeStoreValueRemove = (int)InvokeOp("JsonTestPath", fakeStoreId, "Hello"); +// Assert.That(fakeStoreValueRemove, Is.EqualTo(0)); +// } +// } + +// [Test] +// public void TestJsonTestPathJson() +// { +// TestHelpers.InMethod(); +//// TestHelpers.EnableLogging(); +// +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : 'One' } }"); +// +// { +// int result = (int)InvokeOp("JsonTestPathJson", storeId, "Hello.World"); +// Assert.That(result, Is.EqualTo(1)); +// } +// +// // Test for path which does not resolve to a value. +// { +// int result = (int)InvokeOp("JsonTestPathJson", storeId, "Hello"); +// Assert.That(result, Is.EqualTo(1)); +// } +// +// { +// int result2 = (int)InvokeOp("JsonTestPathJson", storeId, "foo"); +// Assert.That(result2, Is.EqualTo(0)); +// } +// +// // Test with fake store +// { +// UUID fakeStoreId = TestHelpers.ParseTail(0x500); +// int fakeStoreValueRemove = (int)InvokeOp("JsonTestPathJson", fakeStoreId, "Hello"); +// Assert.That(fakeStoreValueRemove, Is.EqualTo(0)); +// } +// } + + [Test] + public void TestJsonGetArrayLength() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : [ 'one', 2 ] } }"); + + { + int result = (int)InvokeOp("JsonGetArrayLength", storeId, "Hello.World"); + Assert.That(result, Is.EqualTo(2)); + } + + // Test path which is not an array + { + int result = (int)InvokeOp("JsonGetArrayLength", storeId, "Hello"); + Assert.That(result, Is.EqualTo(-1)); + } + + // Test fake path + { + int result = (int)InvokeOp("JsonGetArrayLength", storeId, "foo"); + Assert.That(result, Is.EqualTo(-1)); + } + + // Test fake store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int result = (int)InvokeOp("JsonGetArrayLength", fakeStoreId, "Hello.World"); + Assert.That(result, Is.EqualTo(-1)); + } + } + + [Test] + public void TestJsonGetNodeType() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello' : { 'World' : [ 'one', 2 ] } }"); + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "."); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_OBJECT)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_OBJECT)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello.World"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_ARRAY)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello.World[0]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_VALUE)); + } + + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "Hello.World[1]"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_VALUE)); + } + + // Test for non-existent path + { + int result = (int)InvokeOp("JsonGetNodeType", storeId, "foo"); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + } + + // Test for non-existent store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int result = (int)InvokeOp("JsonGetNodeType", fakeStoreId, "."); + Assert.That(result, Is.EqualTo(JsonStoreScriptModule.JSON_NODETYPE_UNDEF)); + } + } + + [Test] + public void TestJsonList2Path() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Invoking these methods directly since I just couldn't get comms module invocation to work for some reason + // - some confusion with the methods that take a params object[] invocation. + { + string result = m_jssm.JsonList2Path(UUID.Zero, UUID.Zero, new object[] { "foo" }); + Assert.That(result, Is.EqualTo("{foo}")); + } + + { + string result = m_jssm.JsonList2Path(UUID.Zero, UUID.Zero, new object[] { "foo", "bar" }); + Assert.That(result, Is.EqualTo("{foo}.{bar}")); + } + + { + string result = m_jssm.JsonList2Path(UUID.Zero, UUID.Zero, new object[] { "foo", 1, "bar" }); + Assert.That(result, Is.EqualTo("{foo}.[1].{bar}")); + } + } + + [Test] + public void TestJsonSetValue() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing periods with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun.Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun.Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // *** Test [] *** + + // Test setting a key containing unbalanced ] without delineation. Expecting failure + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun]Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun]Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced [ without delineation. Expecting failure + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun[Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun[Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced [] without delineation. Expecting failure + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun[]Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun[]Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced ] with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun]Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun]Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing unbalanced [ with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun[Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun[Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing empty balanced [] with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun[]Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun[]Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + +// // Commented out as this currently unexpectedly fails. +// // Test setting a key containing brackets around an integer with delineation +// { +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); +// +// int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun[0]Circus}", "Times"); +// Assert.That(result, Is.EqualTo(1)); +// +// string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun[0]Circus}"); +// Assert.That(value, Is.EqualTo("Times")); +// } + + // *** Test {} *** + + // Test setting a key containing unbalanced } without delineation. Expecting failure (?) + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun}Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun}Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting a key containing unbalanced { without delineation. Expecting failure (?) + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun{Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun}Circus"); + Assert.That(value, Is.EqualTo("")); + } + +// // Commented out as this currently unexpectedly fails. +// // Test setting a key containing unbalanced } +// { +// UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); +// +// int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun}Circus}", "Times"); +// Assert.That(result, Is.EqualTo(0)); +// } + + // Test setting a key containing unbalanced { with delineation + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun{Circus}", "Times"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun{Circus}"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Test setting a key containing balanced {} with delineation. This should fail. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "{Fun{Filled}Circus}", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "{Fun{Filled}Circus}"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting to location that does not exist. This should fail. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{}"); + + int result = (int)InvokeOp("JsonSetValue", storeId, "Fun.Circus", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun.Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test with fake store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int fakeStoreValueSet = (int)InvokeOp("JsonSetValue", fakeStoreId, "Hello", "World"); + Assert.That(fakeStoreValueSet, Is.EqualTo(0)); + } + } + + [Test] + public void TestJsonSetJson() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Single quoted token case + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun", "'Times'"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun"); + Assert.That(value, Is.EqualTo("Times")); + } + + // Sub-tree case + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun", "{ 'Filled' : 'Times' }"); + Assert.That(result, Is.EqualTo(1)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun.Filled"); + Assert.That(value, Is.EqualTo("Times")); + } + + // If setting single strings in JsonSetValueJson, these must be single quoted tokens, not bare strings. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun", "Times"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun"); + Assert.That(value, Is.EqualTo("")); + } + + // Test setting to location that does not exist. This should fail. + { + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ }"); + + int result = (int)InvokeOp("JsonSetJson", storeId, "Fun.Circus", "'Times'"); + Assert.That(result, Is.EqualTo(0)); + + string value = (string)InvokeOp("JsonGetValue", storeId, "Fun.Circus"); + Assert.That(value, Is.EqualTo("")); + } + + // Test with fake store + { + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + int fakeStoreValueSet = (int)InvokeOp("JsonSetJson", fakeStoreId, "Hello", "'World'"); + Assert.That(fakeStoreValueSet, Is.EqualTo(0)); + } + } + + /// + /// Test for writing json to a notecard + /// + /// + /// TODO: Really needs to test correct receipt of the link_message event. Could do this by directly fetching + /// it via the MockScriptEngine or perhaps by a dummy script instance. + /// + [Test] + public void TestJsonWriteNotecard() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, TestHelpers.ParseTail(0x1)); + m_scene.AddSceneObject(so); + + UUID storeId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello':'World' }"); + + { + string notecardName = "nc1"; + + // Write notecard + UUID writeNotecardRequestId = (UUID)InvokeOpOnHost("JsonWriteNotecard", so.UUID, storeId, "", notecardName); + Assert.That(writeNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + TaskInventoryItem nc1Item = so.RootPart.Inventory.GetInventoryItem(notecardName); + Assert.That(nc1Item, Is.Not.Null); + + // TODO: Should independently check the contents. + } + + // TODO: Write partial test + + { + // Try to write notecard for a bad path + // In this case we do get a request id but no notecard is written. + string badPathNotecardName = "badPathNotecardName"; + + UUID writeNotecardBadPathRequestId + = (UUID)InvokeOpOnHost("JsonWriteNotecard", so.UUID, storeId, "flibble", badPathNotecardName); + Assert.That(writeNotecardBadPathRequestId, Is.Not.EqualTo(UUID.Zero)); + + TaskInventoryItem badPathItem = so.RootPart.Inventory.GetInventoryItem(badPathNotecardName); + Assert.That(badPathItem, Is.Null); + } + + { + // Test with fake store + // In this case we do get a request id but no notecard is written. + string fakeStoreNotecardName = "fakeStoreNotecardName"; + + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + UUID fakeStoreWriteNotecardValue + = (UUID)InvokeOpOnHost("JsonWriteNotecard", so.UUID, fakeStoreId, "", fakeStoreNotecardName); + Assert.That(fakeStoreWriteNotecardValue, Is.Not.EqualTo(UUID.Zero)); + + TaskInventoryItem fakeStoreItem = so.RootPart.Inventory.GetInventoryItem(fakeStoreNotecardName); + Assert.That(fakeStoreItem, Is.Null); + } + } + + /// + /// Test for reading json from a notecard + /// + /// + /// TODO: Really needs to test correct receipt of the link_message event. Could do this by directly fetching + /// it via the MockScriptEngine or perhaps by a dummy script instance. + /// + [Test] + public void TestJsonReadNotecard() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string notecardName = "nc1"; + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, TestHelpers.ParseTail(0x1)); + m_scene.AddSceneObject(so); + + UUID creatingStoreId = (UUID)InvokeOp("JsonCreateStore", "{ 'Hello':'World' }"); + + // Write notecard + InvokeOpOnHost("JsonWriteNotecard", so.UUID, creatingStoreId, "", notecardName); + + { + // Read notecard + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{}"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("World")); + } + + { + // Read notecard to new single component path + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{}"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "make", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + + value = (string)InvokeOp("JsonGetValue", receivingStoreId, "make.Hello"); + Assert.That(value, Is.EqualTo("World")); + } + + { + // Read notecard to new multi-component path. This should not work. + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{}"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "make.it", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + + value = (string)InvokeOp("JsonGetValue", receivingStoreId, "make.it.Hello"); + Assert.That(value, Is.EqualTo("")); + } + + { + // Read notecard to existing multi-component path. This should work + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{ 'make' : { 'it' : 'so' } }"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "make.it", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + + value = (string)InvokeOp("JsonGetValue", receivingStoreId, "make.it.Hello"); + Assert.That(value, Is.EqualTo("World")); + } + + { + // Read notecard to invalid path. This should not work. + UUID receivingStoreId = (UUID)InvokeOp("JsonCreateStore", "{ 'make' : { 'it' : 'so' } }"); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, receivingStoreId, "/", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", receivingStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + } + + { + // Try read notecard to fake store. + UUID fakeStoreId = TestHelpers.ParseTail(0x500); + UUID readNotecardRequestId = (UUID)InvokeOpOnHost("JsonReadNotecard", so.UUID, fakeStoreId, "", notecardName); + Assert.That(readNotecardRequestId, Is.Not.EqualTo(UUID.Zero)); + + string value = (string)InvokeOp("JsonGetValue", fakeStoreId, "Hello"); + Assert.That(value, Is.EqualTo("")); + } + } + + public object DummyTestMethod(object o1, object o2, object o3, object o4, object o5) { return null; } + } +} diff --git a/Tests/OpenSim.Region.OptionalModules.Tests/World/NPC/Tests/NPCModuleTests.cs b/Tests/OpenSim.Region.OptionalModules.Tests/World/NPC/Tests/NPCModuleTests.cs new file mode 100644 index 00000000000..9a1ea732748 --- /dev/null +++ b/Tests/OpenSim.Region.OptionalModules.Tests/World/NPC/Tests/NPCModuleTests.cs @@ -0,0 +1,486 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using log4net; +using Nini.Config; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.Attachments; +using OpenSim.Region.CoreModules.Avatar.AvatarFactory; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.CoreModules.Framework.UserManagement; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Avatar; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.AvatarService; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.OptionalModules.World.NPC.Tests +{ + [TestFixture] + public class NPCModuleTests : OpenSimTestCase + { + private TestScene m_scene; + private AvatarFactoryModule m_afMod; + private UserManagementModule m_umMod; + private AttachmentsModule m_attMod; + private NPCModule m_npcMod; + + [TestFixtureSetUp] + public void FixtureInit() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.None; + } + + [TestFixtureTearDown] + public void TearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten not to worry about such things. + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + public void SetUpScene() + { + SetUpScene(256, 256); + } + + public void SetUpScene(uint sizeX, uint sizeY) + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("NPC"); + config.Configs["NPC"].Set("Enabled", "true"); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + m_afMod = new AvatarFactoryModule(); + m_umMod = new UserManagementModule(); + m_attMod = new AttachmentsModule(); + m_npcMod = new NPCModule(); + + m_scene = new SceneHelpers().SetupScene("test scene", UUID.Random(), 1000, 1000, sizeX, sizeY, config); + SceneHelpers.SetupSceneModules(m_scene, config, m_afMod, m_umMod, m_attMod, m_npcMod, new BasicInventoryAccessModule()); + } + + [Test] + public void TestCreate() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + SetUpScene(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); +// ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); + + // 8 is the index of the first baked texture in AvatarAppearance + UUID originalFace8TextureId = TestHelpers.ParseTail(0x10); + Primitive.TextureEntry originalTe = new Primitive.TextureEntry(UUID.Zero); + Primitive.TextureEntryFace originalTef = originalTe.CreateFace(8); + originalTef.TextureID = originalFace8TextureId; + + // We also need to add the texture to the asset service, otherwise the AvatarFactoryModule will tell + // ScenePresence.SendInitialData() to reset our entire appearance. + m_scene.AssetService.Store(AssetHelpers.CreateNotecardAsset(originalFace8TextureId)); + + m_afMod.SetAppearance(sp, originalTe, null, new WearableCacheItem[0] ); + + UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + Assert.That(npc, Is.Not.Null); + Assert.That(npc.Appearance.Texture.FaceTextures[8].TextureID, Is.EqualTo(originalFace8TextureId)); + Assert.That(m_umMod.GetUserName(npc.UUID), Is.EqualTo(string.Format("{0} {1}", npc.Firstname, npc.Lastname))); + + IClientAPI client; + Assert.That(m_scene.TryGetClient(npcId, out client), Is.True); + + // Have to account for both SP and NPC. + Assert.That(m_scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(2)); + } + + [Test] + public void TestRemove() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + SetUpScene(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); +// ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); + + Vector3 startPos = new Vector3(128, 128, 30); + UUID npcId = m_npcMod.CreateNPC("John", "Smith", startPos, UUID.Zero, true, m_scene, sp.Appearance); + + m_npcMod.DeleteNPC(npcId, m_scene); + + ScenePresence deletedNpc = m_scene.GetScenePresence(npcId); + + Assert.That(deletedNpc, Is.Null); + IClientAPI client; + Assert.That(m_scene.TryGetClient(npcId, out client), Is.False); + + // Have to account for SP still present. + Assert.That(m_scene.AuthenticateHandler.GetAgentCircuits().Count, Is.EqualTo(1)); + } + + [Test] + public void TestCreateWithAttachments() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(); + + UUID userId = TestHelpers.ParseTail(0x1); + UserAccountHelpers.CreateUserWithInventory(m_scene, userId); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + + UUID attItemId = TestHelpers.ParseTail(0x2); + UUID attAssetId = TestHelpers.ParseTail(0x3); + string attName = "att"; + + UserInventoryHelpers.CreateInventoryItem(m_scene, attName, attItemId, attAssetId, sp.UUID, InventoryType.Object); + + m_attMod.RezSingleAttachmentFromInventory(sp, attItemId, (uint)AttachmentPoint.Chest); + + UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + // Check scene presence status + Assert.That(npc.HasAttachments(), Is.True); + List attachments = npc.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + + // Just for now, we won't test the name since this is (wrongly) the asset part name rather than the item + // name. TODO: Do need to fix ultimately since the item may be renamed before being passed on to an NPC. +// Assert.That(attSo.Name, Is.EqualTo(attName)); + + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.That(attSo.OwnerID, Is.EqualTo(npc.UUID)); + } + + [Test] + public void TestCreateWithMultiAttachments() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(); +// m_attMod.DebugLevel = 1; + + UUID userId = TestHelpers.ParseTail(0x1); + UserAccountHelpers.CreateUserWithInventory(m_scene, userId); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + + InventoryItemBase att1Item + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "att1", TestHelpers.ParseTail(0x2), TestHelpers.ParseTail(0x3), sp.UUID, InventoryType.Object); + InventoryItemBase att2Item + = UserInventoryHelpers.CreateInventoryItem( + m_scene, "att2", TestHelpers.ParseTail(0x12), TestHelpers.ParseTail(0x13), sp.UUID, InventoryType.Object); + + m_attMod.RezSingleAttachmentFromInventory(sp, att1Item.ID, (uint)AttachmentPoint.Chest); + m_attMod.RezSingleAttachmentFromInventory(sp, att2Item.ID, (uint)AttachmentPoint.Chest | 0x80); + + UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + // Check scene presence status + Assert.That(npc.HasAttachments(), Is.True); + List attachments = npc.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(2)); + + // Just for now, we won't test the name since this is (wrongly) the asset part name rather than the item + // name. TODO: Do need to fix ultimately since the item may be renamed before being passed on to an NPC. +// Assert.That(attSo.Name, Is.EqualTo(attName)); + + TestAttachedObject(attachments[0], AttachmentPoint.Chest, npc.UUID); + TestAttachedObject(attachments[1], AttachmentPoint.Chest, npc.UUID); + + // Attached objects on the same point must have different FromItemIDs to be shown to other avatars, at least + // on Singularity 1.8.5. Otherwise, only one (the first ObjectUpdate sent) appears. + Assert.AreNotEqual(attachments[0].FromItemID, attachments[1].FromItemID); + } + + private void TestAttachedObject(SceneObjectGroup attSo, AttachmentPoint attPoint, UUID ownerId) + { + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)attPoint)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.That(attSo.OwnerID, Is.EqualTo(ownerId)); + } + + [Test] + public void TestLoadAppearance() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + SetUpScene(); + + UUID userId = TestHelpers.ParseTail(0x1); + UserAccountHelpers.CreateUserWithInventory(m_scene, userId); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + + UUID npcId = m_npcMod.CreateNPC("John", "Smith", new Vector3(128, 128, 30), UUID.Zero, true, m_scene, sp.Appearance); + + // Now add the attachment to the original avatar and use that to load a new appearance + // TODO: Could also run tests loading from a notecard though this isn't much different for our purposes here + UUID attItemId = TestHelpers.ParseTail(0x2); + UUID attAssetId = TestHelpers.ParseTail(0x3); + string attName = "att"; + + UserInventoryHelpers.CreateInventoryItem(m_scene, attName, attItemId, attAssetId, sp.UUID, InventoryType.Object); + + m_attMod.RezSingleAttachmentFromInventory(sp, attItemId, (uint)AttachmentPoint.Chest); + + m_npcMod.SetNPCAppearance(npcId, sp.Appearance, m_scene); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + + // Check scene presence status + Assert.That(npc.HasAttachments(), Is.True); + List attachments = npc.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + + // Just for now, we won't test the name since this is (wrongly) the asset part name rather than the item + // name. TODO: Do need to fix ultimately since the item may be renamed before being passed on to an NPC. +// Assert.That(attSo.Name, Is.EqualTo(attName)); + + Assert.That(attSo.AttachmentPoint, Is.EqualTo((byte)AttachmentPoint.Chest)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + Assert.That(attSo.OwnerID, Is.EqualTo(npc.UUID)); + } + + [Test] + public void TestMove() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); +// ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); + + Vector3 startPos = new Vector3(128, 128, 30); + UUID npcId = m_npcMod.CreateNPC("John", "Smith", startPos, UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + + // For now, we'll make the scene presence fly to simplify this test, but this needs to change. + npc.Flying = true; + + m_scene.Update(1); + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + + Vector3 targetPos = startPos + new Vector3(0, 10, 0); + m_npcMod.MoveToTarget(npc.UUID, m_scene, targetPos, false, false, false); + + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + //Assert.That(npc.Rotation, Is.EqualTo(new Quaternion(0, 0, 0.7071068f, 0.7071068f))); + Assert.That( + npc.Rotation, new QuaternionToleranceConstraint(new Quaternion(0, 0, 0.7071068f, 0.7071068f), 0.000001)); + + m_scene.Update(1); + + // We should really check the exact figure. + Assert.That(npc.AbsolutePosition.X, Is.EqualTo(startPos.X)); + Assert.That(npc.AbsolutePosition.Y, Is.GreaterThan(startPos.Y)); + Assert.That(npc.AbsolutePosition.Z, Is.EqualTo(startPos.Z)); + Assert.That(npc.AbsolutePosition.Z, Is.LessThan(targetPos.X)); + + m_scene.Update(10); + + double distanceToTarget = Util.GetDistanceTo(npc.AbsolutePosition, targetPos); + Assert.That(distanceToTarget, Is.LessThan(1), "NPC not within 1 unit of target position on first move"); + Assert.That(npc.AbsolutePosition, Is.EqualTo(targetPos)); + Assert.That(npc.AgentControlFlags, Is.EqualTo((uint)AgentManager.ControlFlags.NONE)); + + // Try a second movement + startPos = npc.AbsolutePosition; + targetPos = startPos + new Vector3(10, 0, 0); + m_npcMod.MoveToTarget(npc.UUID, m_scene, targetPos, false, false, false); + + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); +// Assert.That(npc.Rotation, Is.EqualTo(new Quaternion(0, 0, 0, 1))); + Assert.That( + npc.Rotation, new QuaternionToleranceConstraint(new Quaternion(0, 0, 0, 1), 0.000001)); + + m_scene.Update(1); + + // We should really check the exact figure. + Assert.That(npc.AbsolutePosition.X, Is.GreaterThan(startPos.X)); + Assert.That(npc.AbsolutePosition.X, Is.LessThan(targetPos.X)); + Assert.That(npc.AbsolutePosition.Y, Is.EqualTo(startPos.Y)); + Assert.That(npc.AbsolutePosition.Z, Is.EqualTo(startPos.Z)); + + m_scene.Update(10); + + distanceToTarget = Util.GetDistanceTo(npc.AbsolutePosition, targetPos); + Assert.That(distanceToTarget, Is.LessThan(1), "NPC not within 1 unit of target position on second move"); + Assert.That(npc.AbsolutePosition, Is.EqualTo(targetPos)); + } + + [Test] + public void TestMoveInVarRegion() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(512, 512); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); +// ScenePresence originalAvatar = scene.GetScenePresence(originalClient.AgentId); + + Vector3 startPos = new Vector3(128, 246, 30); + UUID npcId = m_npcMod.CreateNPC("John", "Smith", startPos, UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + + // For now, we'll make the scene presence fly to simplify this test, but this needs to change. + npc.Flying = true; + + m_scene.Update(1); + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + + Vector3 targetPos = startPos + new Vector3(0, 20, 0); + m_npcMod.MoveToTarget(npc.UUID, m_scene, targetPos, false, false, false); + + Assert.That(npc.AbsolutePosition, Is.EqualTo(startPos)); + //Assert.That(npc.Rotation, Is.EqualTo(new Quaternion(0, 0, 0.7071068f, 0.7071068f))); + Assert.That( + npc.Rotation, new QuaternionToleranceConstraint(new Quaternion(0, 0, 0.7071068f, 0.7071068f), 0.000001)); + + m_scene.Update(1); + + // We should really check the exact figure. + Assert.That(npc.AbsolutePosition.X, Is.EqualTo(startPos.X)); + Assert.That(npc.AbsolutePosition.Y, Is.GreaterThan(startPos.Y)); + Assert.That(npc.AbsolutePosition.Z, Is.EqualTo(startPos.Z)); + Assert.That(npc.AbsolutePosition.Z, Is.LessThan(targetPos.X)); + + for (int i = 0; i < 20; i++) + { + m_scene.Update(1); +// Console.WriteLine("pos: {0}", npc.AbsolutePosition); + } + + double distanceToTarget = Util.GetDistanceTo(npc.AbsolutePosition, targetPos); + Assert.That(distanceToTarget, Is.LessThan(1), "NPC not within 1 unit of target position on first move"); + Assert.That(npc.AbsolutePosition, Is.EqualTo(targetPos)); + Assert.That(npc.AgentControlFlags, Is.EqualTo((uint)AgentManager.ControlFlags.NONE)); + } + + [Test] + public void TestSitAndStandWithSitTarget() + { + TestHelpers.InMethod(); +// log4net.Config.XmlConfigurator.Configure(); + + SetUpScene(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + + Vector3 startPos = new Vector3(128, 128, 30); + UUID npcId = m_npcMod.CreateNPC("John", "Smith", startPos, UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + + part.SitTargetPosition = new Vector3(0, 0, 1); + m_npcMod.Sit(npc.UUID, part.UUID, m_scene); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(npcId)); + Assert.That(npc.ParentID, Is.EqualTo(part.LocalId)); +// Assert.That( +// npc.AbsolutePosition, +// Is.EqualTo(part.AbsolutePosition + part.SitTargetPosition + ScenePresence.SIT_TARGET_ADJUSTMENT)); + + m_npcMod.Stand(npc.UUID, m_scene); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(npc.ParentID, Is.EqualTo(0)); + } + + [Test] + public void TestSitAndStandWithNoSitTarget() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + SetUpScene(); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, TestHelpers.ParseTail(0x1)); + + // FIXME: To get this to work for now, we are going to place the npc right next to the target so that + // the autopilot doesn't trigger + Vector3 startPos = new Vector3(1, 1, 1); + + UUID npcId = m_npcMod.CreateNPC("John", "Smith", startPos, UUID.Zero, true, m_scene, sp.Appearance); + + ScenePresence npc = m_scene.GetScenePresence(npcId); + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + + m_npcMod.Sit(npc.UUID, part.UUID, m_scene); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(npc.ParentID, Is.EqualTo(part.LocalId)); + + // We should really be using the NPC size but this would mean preserving the physics actor since it is + // removed on sit. + Assert.That( + npc.AbsolutePosition, + Is.EqualTo(part.AbsolutePosition + new Vector3(0, 0, sp.PhysicsActor.Size.Z / 2))); + + m_npcMod.Stand(npc.UUID, m_scene); + + Assert.That(part.SitTargetAvatar, Is.EqualTo(UUID.Zero)); + Assert.That(npc.ParentID, Is.EqualTo(0)); + } + } +} diff --git a/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BasicVehicles.cs b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BasicVehicles.cs new file mode 100644 index 00000000000..a7e56a7bb33 --- /dev/null +++ b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BasicVehicles.cs @@ -0,0 +1,152 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; + +using NUnit.Framework; + +using OpenSim.Framework; +using OpenSim.Region.PhysicsModule.BulletS; +using OpenSim.Region.PhysicsModule.SharedBase; +using OpenSim.Tests.Common; + +using OpenMetaverse; + +namespace OpenSim.Region.PhysicsModule.BulletS.Tests +{ + [TestFixture] + public class BasicVehicles : OpenSimTestCase + { + // Documentation on attributes: http://www.nunit.org/index.php?p=attributes&r=2.6.1 + // Documentation on assertions: http://www.nunit.org/index.php?p=assertions&r=2.6.1 + + BSScene PhysicsScene { get; set; } + BSPrim TestVehicle { get; set; } + Vector3 TestVehicleInitPosition { get; set; } + float simulationTimeStep = 0.089f; + + [OneTimeSetUp] + public void Init() + { + Dictionary engineParams = new Dictionary(); + engineParams.Add("VehicleEnableAngularVerticalAttraction", "true"); + engineParams.Add("VehicleAngularVerticalAttractionAlgorithm", "1"); + PhysicsScene = BulletSimTestsUtil.CreateBasicPhysicsEngine(engineParams); + + PrimitiveBaseShape pbs = PrimitiveBaseShape.CreateSphere(); + Vector3 pos = new Vector3(100.0f, 100.0f, 0f); + pos.Z = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(pos) + 2f; + TestVehicleInitPosition = pos; + Vector3 size = new Vector3(1f, 1f, 1f); + pbs.Scale = size; + Quaternion rot = Quaternion.Identity; + bool isPhys = false; + uint localID = 123; + + PhysicsScene.AddPrimShape("testPrim", pbs, pos, size, rot, isPhys, localID); + TestVehicle = (BSPrim)PhysicsScene.PhysObjects[localID]; + // The actual prim shape creation happens at taint time + PhysicsScene.ProcessTaints(); + + } + + [OneTimeTearDown] + public void TearDown() + { + if (PhysicsScene != null) + { + // The Dispose() will also free any physical objects in the scene + PhysicsScene.Dispose(); + PhysicsScene = null; + } + } + + [TestCase(2f, 0.2f, 0.25f, 0.25f, 0.25f)] + [TestCase(2f, 0.2f, -0.25f, 0.25f, 0.25f)] + [TestCase(2f, 0.2f, 0.25f, -0.25f, 0.25f)] + [TestCase(2f, 0.2f, -0.25f, -0.25f, 0.25f)] + // [TestCase(2f, 0.2f, 0.785f, 0.0f, 0.25f) /*, "Leaning 45 degrees to the side" */] + // [TestCase(2f, 0.2f, 1.650f, 0.0f, 0.25f) /*, "Leaning more than 90 degrees to the side" */] + // [TestCase(2f, 0.2f, 2.750f, 0.0f, 0.25f) /*, "Almost upside down, tipped right" */] + // [TestCase(2f, 0.2f,-2.750f, 0.0f, 0.25f) /*, "Almost upside down, tipped left" */] + // [TestCase(2f, 0.2f, 0.0f, 0.785f, 0.25f) /*, "Tipped back 45 degrees" */] + // [TestCase(2f, 0.2f, 0.0f, 1.650f, 0.25f) /*, "Tipped back more than 90 degrees" */] + // [TestCase(2f, 0.2f, 0.0f, 2.750f, 0.25f) /*, "Almost upside down, tipped back" */] + // [TestCase(2f, 0.2f, 0.0f,-2.750f, 0.25f) /*, "Almost upside down, tipped forward" */] + public void AngularVerticalAttraction(float timeScale, float efficiency, float initRoll, float initPitch, float initYaw) + { + // Enough simulation steps to cover the timescale the operation should take + int simSteps = (int)(timeScale / simulationTimeStep) + 1; + + // Tip the vehicle + Quaternion initOrientation = Quaternion.CreateFromEulers(initRoll, initPitch, initYaw); + TestVehicle.Orientation = initOrientation; + + TestVehicle.Position = TestVehicleInitPosition; + + // The vehicle controller is not enabled directly (by setting a vehicle type). + // Instead the appropriate values are set and calls are made just the parts of the + // controller we want to exercise. Stepping the physics engine then applies + // the actions of that one feature. + BSDynamics vehicleActor = TestVehicle.GetVehicleActor(true /* createIfNone */); + if (vehicleActor != null) + { + vehicleActor.ProcessFloatVehicleParam(Vehicle.VERTICAL_ATTRACTION_EFFICIENCY, efficiency); + vehicleActor.ProcessFloatVehicleParam(Vehicle.VERTICAL_ATTRACTION_TIMESCALE, timeScale); + // vehicleActor.enableAngularVerticalAttraction = true; + + TestVehicle.IsPhysical = true; + PhysicsScene.ProcessTaints(); + + // Step the simulator a bunch of times and vertical attraction should orient the vehicle up + for (int ii = 0; ii < simSteps; ii++) + { + vehicleActor.ForgetKnownVehicleProperties(); + vehicleActor.ComputeAngularVerticalAttraction(); + vehicleActor.PushKnownChanged(); + + PhysicsScene.Simulate(simulationTimeStep); + } + } + + TestVehicle.IsPhysical = false; + PhysicsScene.ProcessTaints(); + + // After these steps, the vehicle should be upright + /* + float finalRoll, finalPitch, finalYaw; + TestVehicle.Orientation.GetEulerAngles(out finalRoll, out finalPitch, out finalYaw); + Assert.That(finalRoll, Is.InRange(-0.01f, 0.01f)); + Assert.That(finalPitch, Is.InRange(-0.01f, 0.01f)); + Assert.That(finalYaw, Is.InRange(initYaw - 0.1f, initYaw + 0.1f)); + */ + + Vector3 upPointer = Vector3.UnitZ * TestVehicle.Orientation; + Assert.That(upPointer.Z, Is.GreaterThan(0.99f)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTests.cs b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTests.cs new file mode 100644 index 00000000000..95e53a62858 --- /dev/null +++ b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTests.cs @@ -0,0 +1,50 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using NUnit.Framework; + +using OpenSim.Tests.Common; + +namespace OpenSim.Region.PhysicsModule.BulletS.Tests +{ + [TestFixture] + public class BulletSimTests : OpenSimTestCase + { + // Documentation on attributes: http://www.nunit.org/index.php?p=attributes&r=2.6.1 + // Documentation on assertions: http://www.nunit.org/index.php?p=assertions&r=2.6.1 + + [OneTimeSetUp] + public void Init() + { + } + + [OneTimeTearDown] + public void TearDown() + { + } + } +} diff --git a/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTestsUtil.cs b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTestsUtil.cs new file mode 100644 index 00000000000..d49b6ce6ca7 --- /dev/null +++ b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/BulletSimTestsUtil.cs @@ -0,0 +1,109 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Collections.Generic; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Region; +using OpenSim.Region.PhysicsModule.SharedBase; +using OpenSim.Region.PhysicsModule.Meshing; +using OpenSim.Region.Framework.Interfaces; + +using OpenMetaverse; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.PhysicsModule.BulletS.Tests +{ + // Utility functions for building up and tearing down the sample physics environments + public static class BulletSimTestsUtil + { + // 'engineName' is the Bullet engine to use. Either null (for unmanaged), "BulletUnmanaged" or "BulletXNA" + // 'params' is a set of keyValue pairs to set in the engine's configuration file (override defaults) + // May be 'null' if there are no overrides. + public static BSScene CreateBasicPhysicsEngine(Dictionary paramOverrides) + { + IConfigSource openSimINI = new IniConfigSource(); + IConfig startupConfig = openSimINI.AddConfig("Startup"); + startupConfig.Set("physics", "BulletSim"); + startupConfig.Set("meshing", "Meshmerizer"); + startupConfig.Set("cacheSculptMaps", "false"); // meshmerizer shouldn't save maps + + IConfig bulletSimConfig = openSimINI.AddConfig("BulletSim"); + // If the caller cares, specify the bullet engine otherwise it will default to "BulletUnmanaged". + // bulletSimConfig.Set("BulletEngine", "BulletUnmanaged"); + // bulletSimConfig.Set("BulletEngine", "BulletXNA"); + bulletSimConfig.Set("MeshSculptedPrim", "false"); + bulletSimConfig.Set("ForceSimplePrimMeshing", "true"); + if (paramOverrides != null) + { + foreach (KeyValuePair kvp in paramOverrides) + { + bulletSimConfig.Set(kvp.Key, kvp.Value); + } + } + + // If a special directory exists, put detailed logging therein. + // This allows local testing/debugging without having to worry that the build engine will output logs. + if (Directory.Exists("physlogs")) + { + bulletSimConfig.Set("PhysicsLoggingDir", "./physlogs"); + bulletSimConfig.Set("PhysicsLoggingEnabled", "True"); + bulletSimConfig.Set("PhysicsLoggingDoFlush", "True"); + bulletSimConfig.Set("VehicleLoggingEnabled", "True"); + } + + Vector3 regionExtent = new Vector3(Constants.RegionSize, Constants.RegionSize, Constants.RegionHeight); + + RegionInfo info = new RegionInfo(); + info.RegionName = "BSTestRegion"; + info.RegionSizeX = info.RegionSizeY = info.RegionSizeZ = Constants.RegionSize; + Scene scene = new Scene(info); + + IMesher mesher = new Meshmerizer(); + INonSharedRegionModule mod = mesher as INonSharedRegionModule; + mod.Initialise(openSimINI); + mod.AddRegion(scene); + mod.RegionLoaded(scene); + + BSScene pScene = new BSScene(); + mod = (pScene as INonSharedRegionModule); + mod.Initialise(openSimINI); + mod.AddRegion(scene); + mod.RegionLoaded(scene); + + // Since the asset requestor is not initialized, any mesh or sculptie will be a cube. + // In the future, add a fake asset fetcher to get meshes and sculpts. + // bsScene.RequestAssetMethod = ???; + + return pScene; + } + + } +} diff --git a/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/HullCreation.cs b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/HullCreation.cs new file mode 100644 index 00000000000..71bb30ced32 --- /dev/null +++ b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/HullCreation.cs @@ -0,0 +1,195 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +using OpenMetaverse; + +namespace OpenSim.Region.PhysicsModule.BulletS.Tests +{ + [TestFixture] + public class HullCreation : OpenSimTestCase + { + // Documentation on attributes: http://www.nunit.org/index.php?p=attributes&r=2.6.1 + // Documentation on assertions: http://www.nunit.org/index.php?p=assertions&r=2.6.1 + + BSScene PhysicsScene { get; set; } + Vector3 ObjectInitPosition; + + [OneTimeSetUp] + public void Init() + { + + } + + [OneTimeTearDown] + public void TearDown() + { + if (PhysicsScene != null) + { + // The Dispose() will also free any physical objects in the scene + PhysicsScene.Dispose(); + PhysicsScene = null; + } + } + + [TestCase(7, 2, 5f, 5f, 32, 0f)] /* default hull parameters */ + public void GeomHullConvexDecomp(int maxDepthSplit, + int maxDepthSplitForSimpleShapes, + float concavityThresholdPercent, + float volumeConservationThresholdPercent, + int maxVertices, + float maxSkinWidth) + { + // Setup the physics engine to use the C# version of convex decomp + Dictionary engineParams = new Dictionary(); + engineParams.Add("MeshSculptedPrim", "true"); // ShouldMeshSculptedPrim + engineParams.Add("ForceSimplePrimMeshing", "false"); // ShouldForceSimplePrimMeshing + engineParams.Add("UseHullsForPhysicalObjects", "true"); // ShouldUseHullsForPhysicalObjects + engineParams.Add("ShouldRemoveZeroWidthTriangles", "true"); + engineParams.Add("ShouldUseBulletHACD", "false"); + engineParams.Add("ShouldUseSingleConvexHullForPrims", "true"); + engineParams.Add("ShouldUseGImpactShapeForPrims", "false"); + engineParams.Add("ShouldUseAssetHulls", "true"); + + engineParams.Add("CSHullMaxDepthSplit", maxDepthSplit.ToString()); + engineParams.Add("CSHullMaxDepthSplitForSimpleShapes", maxDepthSplitForSimpleShapes.ToString()); + engineParams.Add("CSHullConcavityThresholdPercent", concavityThresholdPercent.ToString()); + engineParams.Add("CSHullVolumeConservationThresholdPercent", volumeConservationThresholdPercent.ToString()); + engineParams.Add("CSHullMaxVertices", maxVertices.ToString()); + engineParams.Add("CSHullMaxSkinWidth", maxSkinWidth.ToString()); + + PhysicsScene = BulletSimTestsUtil.CreateBasicPhysicsEngine(engineParams); + + PrimitiveBaseShape pbs; + Vector3 pos; + Vector3 size; + Quaternion rot; + bool isPhys; + + // Cylinder + pbs = PrimitiveBaseShape.CreateCylinder(); + pos = new Vector3(100.0f, 100.0f, 0f); + pos.Z = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(pos) + 10f; + ObjectInitPosition = pos; + size = new Vector3(2f, 2f, 2f); + pbs.Scale = size; + rot = Quaternion.Identity; + isPhys = true; + uint cylinderLocalID = 123; + PhysicsScene.AddPrimShape("testCylinder", pbs, pos, size, rot, isPhys, cylinderLocalID); + BSPrim primTypeCylinder = (BSPrim)PhysicsScene.PhysObjects[cylinderLocalID]; + + // Hollow Cylinder + pbs = PrimitiveBaseShape.CreateCylinder(); + pbs.ProfileHollow = (ushort)(0.70f * 50000); + pos = new Vector3(110.0f, 110.0f, 0f); + pos.Z = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(pos) + 10f; + ObjectInitPosition = pos; + size = new Vector3(2f, 2f, 2f); + pbs.Scale = size; + rot = Quaternion.Identity; + isPhys = true; + uint hollowCylinderLocalID = 124; + PhysicsScene.AddPrimShape("testHollowCylinder", pbs, pos, size, rot, isPhys, hollowCylinderLocalID); + BSPrim primTypeHollowCylinder = (BSPrim)PhysicsScene.PhysObjects[hollowCylinderLocalID]; + + // Torus + // ProfileCurve = Circle, PathCurve = Curve1 + pbs = PrimitiveBaseShape.CreateSphere(); + pbs.ProfileShape = (byte)ProfileShape.Circle; + pbs.PathCurve = (byte)Extrusion.Curve1; + pbs.PathScaleX = 100; // default hollow info as set in the viewer + pbs.PathScaleY = (int)(.25f / 0.01f) + 200; + pos = new Vector3(120.0f, 120.0f, 0f); + pos.Z = PhysicsScene.TerrainManager.GetTerrainHeightAtXYZ(pos) + 10f; + ObjectInitPosition = pos; + size = new Vector3(2f, 4f, 4f); + pbs.Scale = size; + rot = Quaternion.Identity; + isPhys = true; + uint torusLocalID = 125; + PhysicsScene.AddPrimShape("testTorus", pbs, pos, size, rot, isPhys, torusLocalID); + BSPrim primTypeTorus = (BSPrim)PhysicsScene.PhysObjects[torusLocalID]; + + // The actual prim shape creation happens at taint time + PhysicsScene.ProcessTaints(); + + // Check out the created hull shapes and report their characteristics + ReportShapeGeom(primTypeCylinder); + ReportShapeGeom(primTypeHollowCylinder); + ReportShapeGeom(primTypeTorus); + } + + [TestCase] + public void GeomHullBulletHACD() + { + // Cylinder + // Hollow Cylinder + // Torus + } + + private void ReportShapeGeom(BSPrim prim) + { + if (prim != null) + { + if (prim.PhysShape.HasPhysicalShape) + { + BSShape physShape = prim.PhysShape; + string shapeType = physShape.GetType().ToString(); + switch (shapeType) + { + case "OpenSim.Region.Physics.BulletSPlugin.BSShapeNative": + BSShapeNative nShape = physShape as BSShapeNative; + prim.PhysScene.DetailLog("{0}, type={1}", prim.Name, shapeType); + break; + case "OpenSim.Region.Physics.BulletSPlugin.BSShapeMesh": + BSShapeMesh mShape = physShape as BSShapeMesh; + prim.PhysScene.DetailLog("{0}, mesh, shapeInfo={1}", prim.Name, mShape.shapeInfo); + break; + case "OpenSim.Region.Physics.BulletSPlugin.BSShapeHull": + // BSShapeHull hShape = physShape as BSShapeHull; + // prim.PhysScene.DetailLog("{0}, hull, shapeInfo={1}", prim.Name, hShape.shapeInfo); + break; + case "OpenSim.Region.Physics.BulletSPlugin.BSShapeConvexHull": + BSShapeConvexHull chShape = physShape as BSShapeConvexHull; + prim.PhysScene.DetailLog("{0}, convexHull, shapeInfo={1}", prim.Name, chShape.shapeInfo); + break; + case "OpenSim.Region.Physics.BulletSPlugin.BSShapeCompound": + BSShapeCompound cShape = physShape as BSShapeCompound; + prim.PhysScene.DetailLog("{0}, type={1}", prim.Name, shapeType); + break; + default: + prim.PhysScene.DetailLog("{0}, type={1}", prim.Name, shapeType); + break; + } + } + } + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/OpenSim.Region.PhysicsModule.BulletS.Tests.csproj b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/OpenSim.Region.PhysicsModule.BulletS.Tests.csproj new file mode 100644 index 00000000000..f60a7c5c158 --- /dev/null +++ b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/OpenSim.Region.PhysicsModule.BulletS.Tests.csproj @@ -0,0 +1,45 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/Raycast.cs b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/Raycast.cs new file mode 100644 index 00000000000..198a7082b1f --- /dev/null +++ b/Tests/OpenSim.Region.PhysicsModules.BulletS.Tests/Raycast.cs @@ -0,0 +1,116 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Tests.Common; + +using OpenMetaverse; + +namespace OpenSim.Region.PhysicsModule.BulletS.Tests +{ + [TestFixture] + public class BulletSimRaycast : OpenSimTestCase + { + // Documentation on attributes: http://www.nunit.org/index.php?p=attributes&r=2.6.1 + // Documentation on assertions: http://www.nunit.org/index.php?p=assertions&r=2.6.1 + + BSScene _physicsScene { get; set; } + BSPrim _targetSphere { get; set; } + Vector3 _targetSpherePosition { get; set; } + // float _simulationTimeStep = 0.089f; + + uint _targetLocalID = 123; + + [OneTimeSetUp] + public void Init() + { + Dictionary engineParams = new Dictionary(); + engineParams.Add("UseBulletRaycast", "true"); + _physicsScene = BulletSimTestsUtil.CreateBasicPhysicsEngine(engineParams); + + PrimitiveBaseShape pbs = PrimitiveBaseShape.CreateSphere(); + Vector3 pos = new Vector3(100.0f, 100.0f, 50f); + _targetSpherePosition = pos; + Vector3 size = new Vector3(10f, 10f, 10f); + pbs.Scale = size; + Quaternion rot = Quaternion.Identity; + bool isPhys = false; + + _physicsScene.AddPrimShape("TargetSphere", pbs, pos, size, rot, isPhys, _targetLocalID); + _targetSphere = (BSPrim)_physicsScene.PhysObjects[_targetLocalID]; + // The actual prim shape creation happens at taint time + _physicsScene.ProcessTaints(); + + } + + [OneTimeTearDown] + public void TearDown() + { + if (_physicsScene != null) + { + // The Dispose() will also free any physical objects in the scene + _physicsScene.Dispose(); + _physicsScene = null; + } + } + + // There is a 10x10x10 sphere at <100,100,50> + // Shoot rays around the sphere and verify it hits and doesn't hit + // TestCase parameters are of start and of end and expected result + [TestCase(100f, 50f, 50f, 100f, 150f, 50f, true, "Pass through sphere from front")] + [TestCase(50f, 100f, 50f, 150f, 100f, 50f, true, "Pass through sphere from side")] + [TestCase(50f, 50f, 50f, 150f, 150f, 50f, true, "Pass through sphere diaginally")] + [TestCase(100f, 100f, 100f, 100f, 100f, 20f, true, "Pass through sphere from above")] + [TestCase(20f, 20f, 50f, 80f, 80f, 50f, false, "Not reach sphere")] + [TestCase(50f, 50f, 65f, 150f, 150f, 65f, false, "Passed over sphere")] + public void RaycastAroundObject(float fromX, float fromY, float fromZ, float toX, float toY, float toZ, bool expected, string msg) + { + Vector3 fromPos = new Vector3(fromX, fromY, fromZ); + Vector3 toPos = new Vector3(toX, toY, toZ); + Vector3 direction = toPos - fromPos; + float len = Vector3.Distance(fromPos, toPos); + + List results = _physicsScene.RaycastWorld(fromPos, direction, len, 1); + + if (expected) + { + // The test coordinates should generate a hit + Assert.True(results.Count != 0, msg + ": Did not return a hit but expected to."); + Assert.True(results.Count == 1, msg + ": Raycast returned not just one hit result."); + Assert.True(results[0].ConsumerID == _targetLocalID, msg + ": Raycast returned a collision object other than the target"); + } + else + { + // The test coordinates should not generate a hit + if (results.Count > 0) + { + Assert.False(results.Count > 0, msg + ": Returned a hit at " + results[0].Pos.ToString()); + } + } + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/OpenSim.Region.ScriptEngine.Tests.csproj b/Tests/OpenSim.Region.ScriptEngine.Tests/OpenSim.Region.ScriptEngine.Tests.csproj new file mode 100644 index 00000000000..1ad55dc40ad --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/OpenSim.Region.ScriptEngine.Tests.csproj @@ -0,0 +1,55 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiAvatarTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiAvatarTests.cs new file mode 100644 index 00000000000..abc162978c4 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiAvatarTests.cs @@ -0,0 +1,147 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests relating directly to avatars + /// + [TestFixture] + public class LSL_ApiAvatarTests : OpenSimTestCase + { + /* + protected Scene m_scene; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("OSSL"); + config.Set("DebuggerSafe", false); + config.Set("AllowOSFunctions", "true"); + config.Set("OSFunctionThreatLevel", "Severe"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, initConfigSource); + + m_engine = new YEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + /// + /// Test llSetLinkPrimtiveParams for agents. + /// + /// + /// Also testing entity updates here as well. Possibly that's putting 2 different concerns into one test and + /// this should be separated. + /// + [Test] + public void TestllSetLinkPrimitiveParamsForAgent() + { +/* siting avatars position changed + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + part.RotationOffset = new Quaternion(0.7071068f, 0, 0, 0.7071068f); + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, part, null); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + + // sp has to be less than 10 meters away from 0, 0, 0 (default part position) + Vector3 startPos = new Vector3(3, 2, 1); + sp.AbsolutePosition = startPos; + + sp.HandleAgentRequestSit(sp.ControllingClient, sp.UUID, part.UUID, Vector3.Zero); + + int entityUpdates = 0; + ((TestClient)sp.ControllingClient).OnReceivedEntityUpdate += (entity, flags) => { if (entity is ScenePresence) { entityUpdates++; }}; + + // Test position + { + Vector3 newPos = new Vector3(1, 2, 3); + apiGrp1.llSetLinkPrimitiveParams(2, new LSL_Types.list(ScriptBaseClass.PRIM_POSITION, newPos)); + + Assert.That(sp.OffsetPosition, Is.EqualTo(newPos)); + + m_scene.Update(1); + Assert.That(entityUpdates, Is.EqualTo(1)); + } + + // Test small reposition + { + Vector3 newPos = new Vector3(1.001f, 2, 3); + apiGrp1.llSetLinkPrimitiveParams(2, new LSL_Types.list(ScriptBaseClass.PRIM_POSITION, newPos)); + + Assert.That(sp.OffsetPosition, Is.EqualTo(newPos)); + + m_scene.Update(1); + Assert.That(entityUpdates, Is.EqualTo(2)); + } + + // Test world rotation + { + Quaternion newRot = new Quaternion(0, 0.7071068f, 0, 0.7071068f); + apiGrp1.llSetLinkPrimitiveParams(2, new LSL_Types.list(ScriptBaseClass.PRIM_ROTATION, newRot)); + + Assert.That( + sp.Rotation, new QuaternionToleranceConstraint(part.GetWorldRotation() * newRot, 0.000001)); + + m_scene.Update(1); + Assert.That(entityUpdates, Is.EqualTo(3)); + } + + // Test local rotation + { + Quaternion newRot = new Quaternion(0, 0.7071068f, 0, 0.7071068f); + apiGrp1.llSetLinkPrimitiveParams(2, new LSL_Types.list(ScriptBaseClass.PRIM_ROT_LOCAL, newRot)); + + Assert.That( + sp.Rotation, new QuaternionToleranceConstraint(newRot, 0.000001)); + + m_scene.Update(1); + Assert.That(entityUpdates, Is.EqualTo(4)); + } + + } + */ + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiHttpTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiHttpTests.cs new file mode 100644 index 00000000000..777012660c3 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiHttpTests.cs @@ -0,0 +1,245 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Net; + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; +using OpenSim.Region.CoreModules.Scripting.LSLHttp; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for HTTP related functions in LSL + /// + [TestFixture] + public class LSL_ApiHttpTests : OpenSimTestCase + { + private Scene m_scene; + private MockScriptEngine m_engine; + private UrlModule m_urlModule; + + private TaskInventoryItem m_scriptItem; + private LSL_Api m_lslApi; + + [OneTimeSetUp] + public void TestFixtureSetUp() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TestFixureTearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + uint port = 9999; + MainServer.RemoveHttpServer(port); + + m_engine = new MockScriptEngine(); + m_urlModule = new UrlModule(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Network"); + config.Configs["Network"].Set("ExternalHostNameForLSL", "127.0.0.1"); + m_scene = new SceneHelpers().SetupScene(); + + BaseHttpServer server = new BaseHttpServer(port); + MainServer.AddHttpServer(server); + MainServer.Instance = server; + + server.Start(); + + SceneHelpers.SetupSceneModules(m_scene, config, m_engine, m_urlModule); + + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_scene); + m_scriptItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, so.RootPart); + + // This is disconnected from the actual script - the mock engine does not set up any LSL_Api atm. + // Possibly this could be done and we could obtain it directly from the MockScriptEngine. + m_lslApi = new LSL_Api(); + m_lslApi.Initialize(m_engine, so.RootPart, m_scriptItem); + } + + [TearDown] + public void TearDown() + { + MainServer.Instance.Stop(); + } + + [Test] + public void TestLlReleaseUrl() + { + TestHelpers.InMethod(); + + m_lslApi.llRequestURL(); + string returnedUri = m_engine.PostedEvents[m_scriptItem.ItemID][0].Params[2].ToString(); + + { + // Check that the initial number of URLs is correct + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + } + + { + // Check releasing a non-url + m_lslApi.llReleaseURL("GARBAGE"); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + } + + { + // Check releasing a non-existing url + m_lslApi.llReleaseURL("http://example.com"); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + } + + { + // Check URL release + m_lslApi.llReleaseURL(returnedUri); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls)); + + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(returnedUri); + + bool gotExpectedException = false; + + try + { + using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + {} + } + catch (WebException) + { +// using (HttpWebResponse response = (HttpWebResponse)e.Response) +// gotExpectedException = response.StatusCode == HttpStatusCode.NotFound; + gotExpectedException = true; + } + + Assert.That(gotExpectedException, Is.True); + } + + { + // Check releasing the same URL again + m_lslApi.llReleaseURL(returnedUri); + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls)); + } + } + + [Test] + public void TestLlRequestUrl() + { + TestHelpers.InMethod(); + + string requestId = m_lslApi.llRequestURL(); + Assert.That(requestId, Is.Not.EqualTo(UUID.Zero.ToString())); + string returnedUri; + + { + // Check that URL is correctly set up + Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); + + Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); + + List events = m_engine.PostedEvents[m_scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("http_request")); + + UUID returnKey; + string rawReturnKey = eventParams.Params[0].ToString(); + string method = eventParams.Params[1].ToString(); + returnedUri = eventParams.Params[2].ToString(); + + Assert.That(UUID.TryParse(rawReturnKey, out returnKey), Is.True); + Assert.That(method, Is.EqualTo(ScriptBaseClass.URL_REQUEST_GRANTED)); + Assert.That(Uri.IsWellFormedUriString(returnedUri, UriKind.Absolute), Is.True); + } + + { + // Check that request to URL works. + string testResponse = "Hello World"; + + m_engine.ClearPostedEvents(); + m_engine.PostEventHook + += (itemId, evp) => m_lslApi.llHTTPResponse(evp.Params[0].ToString(), 200, testResponse); + +// Console.WriteLine("Trying {0}", returnedUri); + + AssertHttpResponse(returnedUri, testResponse); + + Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); + + List events = m_engine.PostedEvents[m_scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("http_request")); + + UUID returnKey; + string rawReturnKey = eventParams.Params[0].ToString(); + string method = eventParams.Params[1].ToString(); + string body = eventParams.Params[2].ToString(); + + Assert.That(UUID.TryParse(rawReturnKey, out returnKey), Is.True); + Assert.That(method, Is.EqualTo("GET")); + Assert.That(body, Is.EqualTo("")); + } + } + + private void AssertHttpResponse(string uri, string expectedResponse) + { + HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri); + + using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) + { + using (Stream stream = webResponse.GetResponseStream()) + { + using (StreamReader reader = new StreamReader(stream)) + { + Assert.That(reader.ReadToEnd(), Is.EqualTo(expectedResponse)); + } + } + } + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiInventoryTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiInventoryTests.cs new file mode 100644 index 00000000000..da3ac307209 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiInventoryTests.cs @@ -0,0 +1,298 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.AvatarFactory; +using OpenSim.Region.OptionalModules.World.NPC; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Permissions; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using PermissionMask = OpenSim.Framework.PermissionMask; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for inventory functions in LSL + /// + [TestFixture] + /* + public class LSL_ApiInventoryTests : OpenSimTestCase + { + protected Scene m_scene; + protected XEngine.XEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("Startup"); + config.Set("serverside_object_permissions", true); + config =initConfigSource.AddConfig("Permissions"); + config.Set("permissionmodules", "DefaultPermissionsModule"); + config.Set("serverside_object_permissions", true); + config.Set("propagate_permissions", true); + + config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + config = initConfigSource.AddConfig("OSSL"); + config.Set("DebuggerSafe", false); + config.Set("AllowOSFunctions", "true"); + config.Set("OSFunctionThreatLevel", "Severe"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, initConfigSource, new object[] { new DefaultPermissionsModule() }); + m_engine = new XEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + /// + /// Test giving inventory from an object to an object where both are owned by the same user. + /// + [Test] + public void TestLlGiveInventoryO2OSameOwner() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + string inventoryItemName = "item1"; + + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(1, userId, "so1", 0x10); + m_scene.AddSceneObject(so1); + + // Create an object embedded inside the first + UUID itemId = TestHelpers.ParseTail(0x20); + TaskInventoryHelpers.AddSceneObject(m_scene.AssetService, so1.RootPart, inventoryItemName, itemId, userId); + + LSL_Api api = new LSL_Api(); + api.Initialize(m_engine, so1.RootPart, null); + + // Create a second object + SceneObjectGroup so2 = SceneHelpers.CreateSceneObject(1, userId, "so2", 0x100); + m_scene.AddSceneObject(so2); + + api.llGiveInventory(so2.UUID.ToString(), inventoryItemName); + + // Item has copy permissions so original should stay intact. + List originalItems = so1.RootPart.Inventory.GetInventoryItems(); + Assert.That(originalItems.Count, Is.EqualTo(1)); + + List copiedItems = so2.RootPart.Inventory.GetInventoryItems(inventoryItemName); + Assert.That(copiedItems.Count, Is.EqualTo(1)); + Assert.That(copiedItems[0].Name, Is.EqualTo(inventoryItemName)); + } + + /// + /// Test giving inventory from an object to an object where they have different owners + /// + [Test] + public void TestLlGiveInventoryO2ODifferentOwners() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + string inventoryItemName = "item1"; + + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(1, user1Id, "so1", 0x10); + m_scene.AddSceneObject(so1); + LSL_Api api = new LSL_Api(); + api.Initialize(m_engine, so1.RootPart, null); + + // Create an object embedded inside the first + UUID itemId = TestHelpers.ParseTail(0x20); + TaskInventoryHelpers.AddSceneObject(m_scene.AssetService, so1.RootPart, inventoryItemName, itemId, user1Id); + + // Create a second object + SceneObjectGroup so2 = SceneHelpers.CreateSceneObject(1, user2Id, "so2", 0x100); + m_scene.AddSceneObject(so2); + LSL_Api api2 = new LSL_Api(); + api2.Initialize(m_engine, so2.RootPart, null); + + // *** Firstly, we test where llAllowInventoryDrop() has not been called. *** + api.llGiveInventory(so2.UUID.ToString(), inventoryItemName); + + { + // Item has copy permissions so original should stay intact. + List originalItems = so1.RootPart.Inventory.GetInventoryItems(); + Assert.That(originalItems.Count, Is.EqualTo(1)); + + // Should have not copied + List copiedItems = so2.RootPart.Inventory.GetInventoryItems(inventoryItemName); + Assert.That(copiedItems.Count, Is.EqualTo(0)); + } + + // *** Secondly, we turn on allow inventory drop in the target and retest. *** + api2.llAllowInventoryDrop(1); + api.llGiveInventory(so2.UUID.ToString(), inventoryItemName); + + { + // Item has copy permissions so original should stay intact. + List originalItems = so1.RootPart.Inventory.GetInventoryItems(); + Assert.That(originalItems.Count, Is.EqualTo(1)); + + // Should now have copied. + List copiedItems = so2.RootPart.Inventory.GetInventoryItems(inventoryItemName); + Assert.That(copiedItems.Count, Is.EqualTo(1)); + Assert.That(copiedItems[0].Name, Is.EqualTo(inventoryItemName)); + } + } + + /// + /// Test giving inventory from an object to an avatar that is not the object's owner. + /// + [Test] + public void TestLlGiveInventoryO2DifferentAvatar() + { + TestHelpers.InMethod(); + // TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + string inventoryItemName = "item1"; + + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(1, user1Id, "so1", 0x10); + m_scene.AddSceneObject(so1); + LSL_Api api = new LSL_Api(); + api.Initialize(m_engine, so1.RootPart, null); + + // Create an object embedded inside the first + UUID itemId = TestHelpers.ParseTail(0x20); + TaskInventoryHelpers.AddSceneObject(m_scene.AssetService, so1.RootPart, inventoryItemName, itemId, user1Id); + + UserAccountHelpers.CreateUserWithInventory(m_scene, user2Id); + + api.llGiveInventory(user2Id.ToString(), inventoryItemName); + + InventoryItemBase receivedItem + = UserInventoryHelpers.GetInventoryItem( + m_scene.InventoryService, user2Id, string.Format("Objects/{0}", inventoryItemName)); + + Assert.IsNotNull(receivedItem); + } + + /// + /// Test giving inventory from an object to an avatar that is not the object's owner and where the next + /// permissions do not include mod. + /// + [Test] + public void TestLlGiveInventoryO2DifferentAvatarNoMod() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + string inventoryItemName = "item1"; + + SceneObjectGroup so1 = SceneHelpers.CreateSceneObject(1, user1Id, "so1", 0x10); + m_scene.AddSceneObject(so1); + LSL_Api api = new LSL_Api(); + api.Initialize(m_engine, so1.RootPart, null); + + // Create an object embedded inside the first + UUID itemId = TestHelpers.ParseTail(0x20); + TaskInventoryItem tii + = TaskInventoryHelpers.AddSceneObject(m_scene.AssetService, so1.RootPart, inventoryItemName, itemId, user1Id); + tii.NextPermissions &= ~((uint)PermissionMask.Modify); + + UserAccountHelpers.CreateUserWithInventory(m_scene, user2Id); + + api.llGiveInventory(user2Id.ToString(), inventoryItemName); + + InventoryItemBase receivedItem + = UserInventoryHelpers.GetInventoryItem( + m_scene.InventoryService, user2Id, string.Format("Objects/{0}", inventoryItemName)); + + Assert.IsNotNull(receivedItem); + Assert.AreEqual(0, receivedItem.CurrentPermissions & (uint)PermissionMask.Modify); + } + + [Test] + public void TestLlRemoteLoadScriptPin() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID user1Id = TestHelpers.ParseTail(0x1); + UUID user2Id = TestHelpers.ParseTail(0x2); + + SceneObjectGroup sourceSo = SceneHelpers.AddSceneObject(m_scene, "sourceSo", user1Id); + m_scene.AddSceneObject(sourceSo); + LSL_Api api = new LSL_Api(); + api.Initialize(m_engine, sourceSo.RootPart, null); + TaskInventoryHelpers.AddScript(m_scene.AssetService, sourceSo.RootPart, "script", "Hello World"); + + SceneObjectGroup targetSo = SceneHelpers.AddSceneObject(m_scene, "targetSo", user1Id); + SceneObjectGroup otherOwnedTargetSo = SceneHelpers.AddSceneObject(m_scene, "otherOwnedTargetSo", user2Id); + + // Test that we cannot load a script when the target pin has never been set (i.e. it is zero) + api.llRemoteLoadScriptPin(targetSo.UUID.ToString(), "script", 0, 0, 0); + Assert.IsNull(targetSo.RootPart.Inventory.GetInventoryItem("script")); + + // Test that we cannot load a script when the given pin does not match the target + targetSo.RootPart.ScriptAccessPin = 5; + api.llRemoteLoadScriptPin(targetSo.UUID.ToString(), "script", 3, 0, 0); + Assert.IsNull(targetSo.RootPart.Inventory.GetInventoryItem("script")); + + // Test that we cannot load into a prim with a different owner + otherOwnedTargetSo.RootPart.ScriptAccessPin = 3; + api.llRemoteLoadScriptPin(otherOwnedTargetSo.UUID.ToString(), "script", 3, 0, 0); + Assert.IsNull(otherOwnedTargetSo.RootPart.Inventory.GetInventoryItem("script")); + + // Test that we can load a script when given pin and dest pin match. + targetSo.RootPart.ScriptAccessPin = 3; + api.llRemoteLoadScriptPin(targetSo.UUID.ToString(), "script", 3, 0, 0); + TaskInventoryItem insertedItem = targetSo.RootPart.Inventory.GetInventoryItem("script"); + Assert.IsNotNull(insertedItem); + + // Test that we can no longer load if access pin is unset + targetSo.RootPart.Inventory.RemoveInventoryItem(insertedItem.ItemID); + Assert.IsNull(targetSo.RootPart.Inventory.GetInventoryItem("script")); + + targetSo.RootPart.ScriptAccessPin = 0; + api.llRemoteLoadScriptPin(otherOwnedTargetSo.UUID.ToString(), "script", 3, 0, 0); + Assert.IsNull(otherOwnedTargetSo.RootPart.Inventory.GetInventoryItem("script")); + } + } + */ +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiLinkingTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiLinkingTests.cs new file mode 100644 index 00000000000..6f07c5614cf --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiLinkingTests.cs @@ -0,0 +1,190 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; +using NUnit.Framework; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenMetaverse.StructuredData; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.AvatarFactory; +using OpenSim.Region.OptionalModules.World.NPC; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for linking functions in LSL + /// + /// + /// This relates to LSL. Actual linking functionality should be tested in the main + /// OpenSim.Region.Framework.Scenes.Tests.SceneObjectLinkingTests. + /// + [TestFixture] + public class LSL_ApiLinkingTests : OpenSimTestCase + { + /* + protected Scene m_scene; + protected XEngine.XEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("OSSL"); + config.Set("DebuggerSafe", false); + config.Set("AllowOSFunctions", "true"); + config.Set("OSFunctionThreatLevel", "Severe"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, initConfigSource); + + m_engine = new XEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + [Test] + public void TestllCreateLink() + { + TestHelpers.InMethod(); + + UUID ownerId = TestHelpers.ParseTail(0x1); + + SceneObjectGroup grp1 = SceneHelpers.CreateSceneObject(2, ownerId, "grp1-", 0x10); + grp1.AbsolutePosition = new Vector3(10, 10, 10); + m_scene.AddSceneObject(grp1); + + // FIXME: This should really be a script item (with accompanying script) + TaskInventoryItem grp1Item + = TaskInventoryHelpers.AddNotecard( + m_scene.AssetService, grp1.RootPart, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); + grp1Item.PermsMask |= ScriptBaseClass.PERMISSION_CHANGE_LINKS; + + SceneObjectGroup grp2 = SceneHelpers.CreateSceneObject(2, ownerId, "grp2-", 0x20); + grp2.AbsolutePosition = new Vector3(20, 20, 20); + + // <180,0,0> + grp2.UpdateGroupRotationR(Quaternion.CreateFromEulers(180 * Utils.DEG_TO_RAD, 0, 0)); + + m_scene.AddSceneObject(grp2); + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, grp1.RootPart, grp1Item); + + apiGrp1.llCreateLink(grp2.UUID.ToString(), ScriptBaseClass.TRUE); + + Assert.That(grp1.Parts.Length, Is.EqualTo(4)); + Assert.That(grp2.IsDeleted, Is.True); + } + + [Test] + public void TestllBreakLink() + { + TestHelpers.InMethod(); + + UUID ownerId = TestHelpers.ParseTail(0x1); + + SceneObjectGroup grp1 = SceneHelpers.CreateSceneObject(2, ownerId, "grp1-", 0x10); + grp1.AbsolutePosition = new Vector3(10, 10, 10); + m_scene.AddSceneObject(grp1); + + // FIXME: This should really be a script item (with accompanying script) + TaskInventoryItem grp1Item + = TaskInventoryHelpers.AddNotecard( + m_scene.AssetService, grp1.RootPart, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); + + grp1Item.PermsMask |= ScriptBaseClass.PERMISSION_CHANGE_LINKS; + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, grp1.RootPart, grp1Item); + + apiGrp1.llBreakLink(2); + + Assert.That(grp1.Parts.Length, Is.EqualTo(1)); + + SceneObjectGroup grp2 = m_scene.GetSceneObjectGroup("grp1-Part1"); + Assert.That(grp2, Is.Not.Null); + } + + [Test] + public void TestllBreakAllLinks() + { + TestHelpers.InMethod(); + + UUID ownerId = TestHelpers.ParseTail(0x1); + + SceneObjectGroup grp1 = SceneHelpers.CreateSceneObject(3, ownerId, "grp1-", 0x10); + grp1.AbsolutePosition = new Vector3(10, 10, 10); + m_scene.AddSceneObject(grp1); + + // FIXME: This should really be a script item (with accompanying script) + TaskInventoryItem grp1Item + = TaskInventoryHelpers.AddNotecard( + m_scene.AssetService, grp1.RootPart, "ncItem", TestHelpers.ParseTail(0x800), TestHelpers.ParseTail(0x900), "Hello World!"); + + grp1Item.PermsMask |= ScriptBaseClass.PERMISSION_CHANGE_LINKS; + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, grp1.RootPart, grp1Item); + + apiGrp1.llBreakAllLinks(); + + { + SceneObjectGroup nowGrp = m_scene.GetSceneObjectGroup("grp1-Part1"); + Assert.That(nowGrp, Is.Not.Null); + Assert.That(nowGrp.Parts.Length, Is.EqualTo(1)); + } + + { + SceneObjectGroup nowGrp = m_scene.GetSceneObjectGroup("grp1-Part2"); + Assert.That(nowGrp, Is.Not.Null); + Assert.That(nowGrp.Parts.Length, Is.EqualTo(1)); + } + + { + SceneObjectGroup nowGrp = m_scene.GetSceneObjectGroup("grp1-Part3"); + Assert.That(nowGrp, Is.Not.Null); + Assert.That(nowGrp.Parts.Length, Is.EqualTo(1)); + } + } + } + */ +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiListTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiListTests.cs new file mode 100644 index 00000000000..b3f07745664 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiListTests.cs @@ -0,0 +1,138 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenMetaverse; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + [TestFixture] + public class LSL_ApiListTests : OpenSimTestCase + { + private LSL_Api m_lslApi; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("OSSL"); + config.Set("DebuggerSafe", false); + config.Set("AllowOSFunctions", "true"); + config.Set("OSFunctionThreatLevel", "Severe"); + Scene scene = new SceneHelpers().SetupScene(); + SceneObjectPart part = SceneHelpers.AddSceneObject(scene).RootPart; + + XEngine.XEngine engine = new XEngine.XEngine(); + engine.Initialise(initConfigSource); + engine.AddRegion(scene); + + m_lslApi = new LSL_Api(); + m_lslApi.Initialize(engine, part, null); + } + + [Test] + public void TestllListFindList() + { + TestHelpers.InMethod(); + + LSL_List src = new LSL_List(new LSL_Integer(1), new LSL_Integer(2), new LSL_Integer(3)); + + { + // Test for a single item that should be found + int result = m_lslApi.llListFindList(src, new LSL_List(new LSL_Integer(4))); + Assert.That(result, Is.EqualTo(-1)); + } + + { + // Test for a single item that should be found + int result = m_lslApi.llListFindList(src, new LSL_List(new LSL_Integer(2))); + Assert.That(result, Is.EqualTo(1)); + } + + { + // Test for a constant that should be found + int result = m_lslApi.llListFindList(src, new LSL_List(ScriptBaseClass.AGENT)); + Assert.That(result, Is.EqualTo(0)); + } + + { + // Test for a list that should be found + int result = m_lslApi.llListFindList(src, new LSL_List(new LSL_Integer(2), new LSL_Integer(3))); + Assert.That(result, Is.EqualTo(1)); + } + + { + // Test for a single item not in the list + int result = m_lslApi.llListFindList(src, new LSL_List(new LSL_Integer(4))); + Assert.That(result, Is.EqualTo(-1)); + } + + { + // Test for something that should not be cast + int result = m_lslApi.llListFindList(src, new LSL_List(new LSL_String("4"))); + Assert.That(result, Is.EqualTo(-1)); + } + + { + // Test for a list not in the list + int result + = m_lslApi.llListFindList( + src, new LSL_List(new LSL_Integer(2), new LSL_Integer(3), new LSL_Integer(4))); + Assert.That(result, Is.EqualTo(-1)); + } + + { + LSL_List srcWithConstants + = new LSL_List(new LSL_Integer(3), ScriptBaseClass.AGENT, ScriptBaseClass.OS_NPC_LAND_AT_TARGET); + + // Test for constants that appears in the source list that should be found + int result + = m_lslApi.llListFindList(srcWithConstants, new LSL_List(new LSL_Integer(1), new LSL_Integer(2))); + + Assert.That(result, Is.EqualTo(1)); + } + } + } + } diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiNotecardTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiNotecardTests.cs new file mode 100644 index 00000000000..31cf1e226e7 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiNotecardTests.cs @@ -0,0 +1,256 @@ +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for notecard related functions in LSL + /// + [TestFixture] + public class LSL_ApiNotecardTests : OpenSimTestCase + { + private Scene m_scene; + private MockScriptEngine m_engine; + + private SceneObjectGroup m_so; + private TaskInventoryItem m_scriptItem; + private LSL_Api m_lslApi; + + [OneTimeSetUp] + public void TestFixtureSetUp() + { + // Don't allow tests to be bamboozled by asynchronous events. Execute everything on the same thread. + Util.FireAndForgetMethod = FireAndForgetMethod.RegressionTest; + } + + [OneTimeTearDown] + public void TestFixureTearDown() + { + // We must set this back afterwards, otherwise later tests will fail since they're expecting multiple + // threads. Possibly, later tests should be rewritten so none of them require async stuff (which regression + // tests really shouldn't). + Util.FireAndForgetMethod = Util.DefaultFireAndForgetMethod; + } + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_engine = new MockScriptEngine(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, new IniConfigSource(), m_engine); + + m_so = SceneHelpers.AddSceneObject(m_scene); + m_scriptItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, m_so.RootPart); + + // This is disconnected from the actual script - the mock engine does not set up any LSL_Api atm. + // Possibly this could be done and we could obtain it directly from the MockScriptEngine. + m_lslApi = new LSL_Api(); + m_lslApi.Initialize(m_engine, m_so.RootPart, m_scriptItem); + } + + [Test] + public void TestLlGetNotecardLine() + { + TestHelpers.InMethod(); + + string[] ncLines = { "One", "Twoè", "Three" }; + + TaskInventoryItem ncItem + = TaskInventoryHelpers.AddNotecard(m_scene.AssetService, m_so.RootPart, "nc", "1", "10", string.Join("\n", ncLines)); + + AssertValidNotecardLine(ncItem.Name, 0, ncLines[0]); + AssertValidNotecardLine(ncItem.Name, 2, ncLines[2]); + AssertValidNotecardLine(ncItem.Name, 3, ScriptBaseClass.EOF); + AssertValidNotecardLine(ncItem.Name, 4, ScriptBaseClass.EOF); + + // XXX: Is this correct or do we really expect no dataserver event to fire at all? + AssertValidNotecardLine(ncItem.Name, -1, ""); + AssertValidNotecardLine(ncItem.Name, -2, ""); + } + + [Test] + public void TestLlGetNotecardLine_NoNotecard() + { + TestHelpers.InMethod(); + + AssertInValidNotecardLine("nc", 0); + } + + [Test] + public void TestLlGetNotecardLine_NotANotecard() + { + TestHelpers.InMethod(); + + TaskInventoryItem ncItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, m_so.RootPart, "nc1", "Not important"); + + AssertInValidNotecardLine(ncItem.Name, 0); + } + + private void AssertValidNotecardLine(string ncName, int lineNumber, string assertLine) + { + string key = m_lslApi.llGetNotecardLine(ncName, lineNumber); + Assert.That(key, Is.Not.EqualTo(UUID.Zero.ToString())); + + Assert.That(m_engine.PostedEvents.Count, Is.EqualTo(1)); + Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); + + List events = m_engine.PostedEvents[m_scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + + Assert.That(eventParams.EventName, Is.EqualTo("dataserver")); + Assert.That(eventParams.Params[0].ToString(), Is.EqualTo(key)); + Assert.That(eventParams.Params[1].ToString(), Is.EqualTo(assertLine)); + + m_engine.ClearPostedEvents(); + } + + private void AssertInValidNotecardLine(string ncName, int lineNumber) + { + string key = m_lslApi.llGetNotecardLine(ncName, lineNumber); + Assert.That(key, Is.EqualTo(UUID.Zero.ToString())); + + Assert.That(m_engine.PostedEvents.Count, Is.EqualTo(0)); + } + +// [Test] +// public void TestLlReleaseUrl() +// { +// TestHelpers.InMethod(); +// +// m_lslApi.llRequestURL(); +// string returnedUri = m_engine.PostedEvents[m_scriptItem.ItemID][0].Params[2].ToString(); +// +// { +// // Check that the initial number of URLs is correct +// Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); +// } +// +// { +// // Check releasing a non-url +// m_lslApi.llReleaseURL("GARBAGE"); +// Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); +// } +// +// { +// // Check releasing a non-existing url +// m_lslApi.llReleaseURL("http://example.com"); +// Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); +// } +// +// { +// // Check URL release +// m_lslApi.llReleaseURL(returnedUri); +// Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls)); +// +// HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(returnedUri); +// +// bool gotExpectedException = false; +// +// try +// { +// using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) +// {} +// } +// catch (WebException e) +// { +// using (HttpWebResponse response = (HttpWebResponse)e.Response) +// gotExpectedException = response.StatusCode == HttpStatusCode.NotFound; +// } +// +// Assert.That(gotExpectedException, Is.True); +// } +// +// { +// // Check releasing the same URL again +// m_lslApi.llReleaseURL(returnedUri); +// Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls)); +// } +// } +// +// [Test] +// public void TestLlRequestUrl() +// { +// TestHelpers.InMethod(); +// +// string requestId = m_lslApi.llRequestURL(); +// Assert.That(requestId, Is.Not.EqualTo(UUID.Zero.ToString())); +// string returnedUri; +// +// { +// // Check that URL is correctly set up +// Assert.That(m_lslApi.llGetFreeURLs().value, Is.EqualTo(m_urlModule.TotalUrls - 1)); +// +// Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); +// +// List events = m_engine.PostedEvents[m_scriptItem.ItemID]; +// Assert.That(events.Count, Is.EqualTo(1)); +// EventParams eventParams = events[0]; +// Assert.That(eventParams.EventName, Is.EqualTo("http_request")); +// +// UUID returnKey; +// string rawReturnKey = eventParams.Params[0].ToString(); +// string method = eventParams.Params[1].ToString(); +// returnedUri = eventParams.Params[2].ToString(); +// +// Assert.That(UUID.TryParse(rawReturnKey, out returnKey), Is.True); +// Assert.That(method, Is.EqualTo(ScriptBaseClass.URL_REQUEST_GRANTED)); +// Assert.That(Uri.IsWellFormedUriString(returnedUri, UriKind.Absolute), Is.True); +// } +// +// { +// // Check that request to URL works. +// string testResponse = "Hello World"; +// +// m_engine.ClearPostedEvents(); +// m_engine.PostEventHook +// += (itemId, evp) => m_lslApi.llHTTPResponse(evp.Params[0].ToString(), 200, testResponse); +// +//// Console.WriteLine("Trying {0}", returnedUri); +// HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(returnedUri); +// +// AssertHttpResponse(returnedUri, testResponse); +// +// Assert.That(m_engine.PostedEvents.ContainsKey(m_scriptItem.ItemID)); +// +// List events = m_engine.PostedEvents[m_scriptItem.ItemID]; +// Assert.That(events.Count, Is.EqualTo(1)); +// EventParams eventParams = events[0]; +// Assert.That(eventParams.EventName, Is.EqualTo("http_request")); +// +// UUID returnKey; +// string rawReturnKey = eventParams.Params[0].ToString(); +// string method = eventParams.Params[1].ToString(); +// string body = eventParams.Params[2].ToString(); +// +// Assert.That(UUID.TryParse(rawReturnKey, out returnKey), Is.True); +// Assert.That(method, Is.EqualTo("GET")); +// Assert.That(body, Is.EqualTo("")); +// } +// } +// +// private void AssertHttpResponse(string uri, string expectedResponse) +// { +// HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(uri); +// +// using (HttpWebResponse webResponse = (HttpWebResponse)webRequest.GetResponse()) +// { +// using (Stream stream = webResponse.GetResponseStream()) +// { +// using (StreamReader reader = new StreamReader(stream)) +// { +// Assert.That(reader.ReadToEnd(), Is.EqualTo(expectedResponse)); +// } +// } +// } +// } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiObjectTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiObjectTests.cs new file mode 100644 index 00000000000..7733e1fd3ef --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiObjectTests.cs @@ -0,0 +1,397 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.AvatarFactory; +using OpenSim.Region.OptionalModules.World.NPC; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + [TestFixture] + public class LSL_ApiObjectTests : OpenSimTestCase + { + /* + private const double VECTOR_COMPONENT_ACCURACY = 0.0000005d; + private const float FLOAT_ACCURACY = 0.00005f; + + protected Scene m_scene; + protected XEngine.XEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, initConfigSource); + + m_engine = new XEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + [Test] + public void TestllGetLinkPrimitiveParams() + { + TestHelpers.InMethod(); + TestHelpers.EnableLogging(); + + UUID ownerId = TestHelpers.ParseTail(0x1); + + SceneObjectGroup grp1 = SceneHelpers.CreateSceneObject(2, ownerId, "grp1-", 0x10); + grp1.AbsolutePosition = new Vector3(10, 11, 12); + m_scene.AddSceneObject(grp1); + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, grp1.RootPart, null); + + // Check simple 1 prim case + { + LSL_List resList + = apiGrp1.llGetLinkPrimitiveParams(1, new LSL_List(new LSL_Integer(ScriptBaseClass.PRIM_ROTATION))); + + Assert.That(resList.Length, Is.EqualTo(1)); + } + + // Check 2 prim case + { + LSL_List resList + = apiGrp1.llGetLinkPrimitiveParams( + 1, + new LSL_List( + new LSL_Integer(ScriptBaseClass.PRIM_ROTATION), + new LSL_Integer(ScriptBaseClass.PRIM_LINK_TARGET), + new LSL_Integer(2), + new LSL_Integer(ScriptBaseClass.PRIM_ROTATION))); + + Assert.That(resList.Length, Is.EqualTo(2)); + } + + // Check invalid parameters are ignored + { + LSL_List resList + = apiGrp1.llGetLinkPrimitiveParams(3, new LSL_List(new LSL_Integer(ScriptBaseClass.PRIM_ROTATION))); + + Assert.That(resList.Length, Is.EqualTo(0)); + } + + // Check all parameters are ignored if an initial bad link is given + { + LSL_List resList + = apiGrp1.llGetLinkPrimitiveParams( + 3, + new LSL_List( + new LSL_Integer(ScriptBaseClass.PRIM_ROTATION), + new LSL_Integer(ScriptBaseClass.PRIM_LINK_TARGET), + new LSL_Integer(1), + new LSL_Integer(ScriptBaseClass.PRIM_ROTATION))); + + Assert.That(resList.Length, Is.EqualTo(0)); + } + + // Check only subsequent parameters are ignored when we hit the first bad link number + { + LSL_List resList + = apiGrp1.llGetLinkPrimitiveParams( + 1, + new LSL_List( + new LSL_Integer(ScriptBaseClass.PRIM_ROTATION), + new LSL_Integer(ScriptBaseClass.PRIM_LINK_TARGET), + new LSL_Integer(3), + new LSL_Integer(ScriptBaseClass.PRIM_ROTATION))); + + Assert.That(resList.Length, Is.EqualTo(1)); + } + } + + [Test] + // llSetPrimitiveParams and llGetPrimitiveParams test. + public void TestllSetPrimitiveParams() + { + TestHelpers.InMethod(); + + // Create Prim1. + Scene scene = new SceneHelpers().SetupScene(); + string obj1Name = "Prim1"; + UUID objUuid = new UUID("00000000-0000-0000-0000-000000000001"); + SceneObjectPart part1 = + new SceneObjectPart(UUID.Zero, PrimitiveBaseShape.Default, + Vector3.Zero, Quaternion.Identity, + Vector3.Zero) { Name = obj1Name, UUID = objUuid }; + Assert.That(scene.AddNewSceneObject(new SceneObjectGroup(part1), false), Is.True); + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, part1, null); + + // Note that prim hollow check is passed with the other prim params in order to allow the + // specification of a different check value from the prim param. A cylinder, prism, sphere, + // torus or ring, with a hole shape of square, is limited to a hollow of 70%. Test 5 below + // specifies a value of 95% and checks to see if 70% was properly returned. + + // Test a sphere. + CheckllSetPrimitiveParams( + apiGrp1, + "test 1", // Prim test identification string + new LSL_Types.Vector3(6.0d, 9.9d, 9.9d), // Prim size + ScriptBaseClass.PRIM_TYPE_SPHERE, // Prim type + ScriptBaseClass.PRIM_HOLE_DEFAULT, // Prim hole type + new LSL_Types.Vector3(0.0d, 0.075d, 0.0d), // Prim cut + 0.80f, // Prim hollow + new LSL_Types.Vector3(0.0d, 0.0d, 0.0d), // Prim twist + new LSL_Types.Vector3(0.32d, 0.76d, 0.0d), // Prim dimple + 0.80f); // Prim hollow check + + // Test a prism. + CheckllSetPrimitiveParams( + apiGrp1, + "test 2", // Prim test identification string + new LSL_Types.Vector3(3.5d, 3.5d, 3.5d), // Prim size + ScriptBaseClass.PRIM_TYPE_PRISM, // Prim type + ScriptBaseClass.PRIM_HOLE_CIRCLE, // Prim hole type + new LSL_Types.Vector3(0.0d, 1.0d, 0.0d), // Prim cut + 0.90f, // Prim hollow + new LSL_Types.Vector3(0.0d, 0.0d, 0.0d), // Prim twist + new LSL_Types.Vector3(2.0d, 1.0d, 0.0d), // Prim taper + new LSL_Types.Vector3(0.0d, 0.0d, 0.0d), // Prim shear + 0.90f); // Prim hollow check + + // Test a box. + CheckllSetPrimitiveParams( + apiGrp1, + "test 3", // Prim test identification string + new LSL_Types.Vector3(3.5d, 3.5d, 3.5d), // Prim size + ScriptBaseClass.PRIM_TYPE_BOX, // Prim type + ScriptBaseClass.PRIM_HOLE_TRIANGLE, // Prim hole type + new LSL_Types.Vector3(0.0d, 1.0d, 0.0d), // Prim cut + 0.99f, // Prim hollow + new LSL_Types.Vector3(1.0d, 0.0d, 0.0d), // Prim twist + new LSL_Types.Vector3(1.0d, 1.0d, 0.0d), // Prim taper + new LSL_Types.Vector3(0.0d, 0.0d, 0.0d), // Prim shear + 0.99f); // Prim hollow check + + // Test a tube. + CheckllSetPrimitiveParams( + apiGrp1, + "test 4", // Prim test identification string + new LSL_Types.Vector3(4.2d, 4.2d, 4.2d), // Prim size + ScriptBaseClass.PRIM_TYPE_TUBE, // Prim type + ScriptBaseClass.PRIM_HOLE_SQUARE, // Prim hole type + new LSL_Types.Vector3(0.0d, 1.0d, 0.0d), // Prim cut + 0.00f, // Prim hollow + new LSL_Types.Vector3(1.0d, -1.0d, 0.0d), // Prim twist + new LSL_Types.Vector3(1.0d, 0.05d, 0.0d), // Prim hole size + // Expression for y selected to test precision problems during byte + // cast in SetPrimitiveShapeParams. + new LSL_Types.Vector3(0.0d, 0.35d + 0.1d, 0.0d), // Prim shear + new LSL_Types.Vector3(0.0d, 1.0d, 0.0d), // Prim profile cut + // Expression for y selected to test precision problems during sbyte + // cast in SetPrimitiveShapeParams. + new LSL_Types.Vector3(-1.0d, 0.70d + 0.1d + 0.1d, 0.0d), // Prim taper + 1.11f, // Prim revolutions + 0.88f, // Prim radius + 0.95f, // Prim skew + 0.00f); // Prim hollow check + + // Test a prism. + CheckllSetPrimitiveParams( + apiGrp1, + "test 5", // Prim test identification string + new LSL_Types.Vector3(3.5d, 3.5d, 3.5d), // Prim size + ScriptBaseClass.PRIM_TYPE_PRISM, // Prim type + ScriptBaseClass.PRIM_HOLE_SQUARE, // Prim hole type + new LSL_Types.Vector3(0.0d, 1.0d, 0.0d), // Prim cut + 0.99f, // Prim hollow + // Expression for x selected to test precision problems during sbyte + // cast in SetPrimitiveShapeBlockParams. + new LSL_Types.Vector3(0.7d + 0.2d, 0.0d, 0.0d), // Prim twist + // Expression for y selected to test precision problems during sbyte + // cast in SetPrimitiveShapeParams. + new LSL_Types.Vector3(2.0d, (1.3d + 0.1d), 0.0d), // Prim taper + new LSL_Types.Vector3(0.0d, 0.0d, 0.0d), // Prim shear + 0.70f); // Prim hollow check + + // Test a sculpted prim. + CheckllSetPrimitiveParams( + apiGrp1, + "test 6", // Prim test identification string + new LSL_Types.Vector3(2.0d, 2.0d, 2.0d), // Prim size + ScriptBaseClass.PRIM_TYPE_SCULPT, // Prim type + "be293869-d0d9-0a69-5989-ad27f1946fd4", // Prim map + ScriptBaseClass.PRIM_SCULPT_TYPE_SPHERE); // Prim sculpt type + } + + // Set prim params for a box, cylinder or prism and check results. + public void CheckllSetPrimitiveParams(LSL_Api api, string primTest, + LSL_Types.Vector3 primSize, int primType, int primHoleType, LSL_Types.Vector3 primCut, + float primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primTaper, LSL_Types.Vector3 primShear, + float primHollowCheck) + { + // Set the prim params. + api.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize, + ScriptBaseClass.PRIM_TYPE, primType, primHoleType, + primCut, primHollow, primTwist, primTaper, primShear)); + + // Get params for prim to validate settings. + LSL_Types.list primParams = + api.llGetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, ScriptBaseClass.PRIM_TYPE)); + + // Validate settings. + CheckllSetPrimitiveParamsVector(primSize, api.llList2Vector(primParams, 0), primTest + " prim size"); + Assert.AreEqual(primType, api.llList2Integer(primParams, 1), + "TestllSetPrimitiveParams " + primTest + " prim type check fail"); + Assert.AreEqual(primHoleType, api.llList2Integer(primParams, 2), + "TestllSetPrimitiveParams " + primTest + " prim hole default check fail"); + CheckllSetPrimitiveParamsVector(primCut, api.llList2Vector(primParams, 3), primTest + " prim cut"); + Assert.AreEqual(primHollowCheck, api.llList2Float(primParams, 4), FLOAT_ACCURACY, + "TestllSetPrimitiveParams " + primTest + " prim hollow check fail"); + CheckllSetPrimitiveParamsVector(primTwist, api.llList2Vector(primParams, 5), primTest + " prim twist"); + CheckllSetPrimitiveParamsVector(primTaper, api.llList2Vector(primParams, 6), primTest + " prim taper"); + CheckllSetPrimitiveParamsVector(primShear, api.llList2Vector(primParams, 7), primTest + " prim shear"); + } + + // Set prim params for a sphere and check results. + public void CheckllSetPrimitiveParams(LSL_Api api, string primTest, + LSL_Types.Vector3 primSize, int primType, int primHoleType, LSL_Types.Vector3 primCut, + float primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primDimple, float primHollowCheck) + { + // Set the prim params. + api.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize, + ScriptBaseClass.PRIM_TYPE, primType, primHoleType, + primCut, primHollow, primTwist, primDimple)); + + // Get params for prim to validate settings. + LSL_Types.list primParams = + api.llGetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, ScriptBaseClass.PRIM_TYPE)); + + // Validate settings. + CheckllSetPrimitiveParamsVector(primSize, api.llList2Vector(primParams, 0), primTest + " prim size"); + Assert.AreEqual(primType, api.llList2Integer(primParams, 1), + "TestllSetPrimitiveParams " + primTest + " prim type check fail"); + Assert.AreEqual(primHoleType, api.llList2Integer(primParams, 2), + "TestllSetPrimitiveParams " + primTest + " prim hole default check fail"); + CheckllSetPrimitiveParamsVector(primCut, api.llList2Vector(primParams, 3), primTest + " prim cut"); + Assert.AreEqual(primHollowCheck, api.llList2Float(primParams, 4), FLOAT_ACCURACY, + "TestllSetPrimitiveParams " + primTest + " prim hollow check fail"); + CheckllSetPrimitiveParamsVector(primTwist, api.llList2Vector(primParams, 5), primTest + " prim twist"); + CheckllSetPrimitiveParamsVector(primDimple, api.llList2Vector(primParams, 6), primTest + " prim dimple"); + } + + // Set prim params for a torus, tube or ring and check results. + public void CheckllSetPrimitiveParams(LSL_Api api, string primTest, + LSL_Types.Vector3 primSize, int primType, int primHoleType, LSL_Types.Vector3 primCut, + float primHollow, LSL_Types.Vector3 primTwist, LSL_Types.Vector3 primHoleSize, + LSL_Types.Vector3 primShear, LSL_Types.Vector3 primProfCut, LSL_Types.Vector3 primTaper, + float primRev, float primRadius, float primSkew, float primHollowCheck) + { + // Set the prim params. + api.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize, + ScriptBaseClass.PRIM_TYPE, primType, primHoleType, + primCut, primHollow, primTwist, primHoleSize, primShear, primProfCut, + primTaper, primRev, primRadius, primSkew)); + + // Get params for prim to validate settings. + LSL_Types.list primParams = + api.llGetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, ScriptBaseClass.PRIM_TYPE)); + + // Valdate settings. + CheckllSetPrimitiveParamsVector(primSize, api.llList2Vector(primParams, 0), primTest + " prim size"); + Assert.AreEqual(primType, api.llList2Integer(primParams, 1), + "TestllSetPrimitiveParams " + primTest + " prim type check fail"); + Assert.AreEqual(primHoleType, api.llList2Integer(primParams, 2), + "TestllSetPrimitiveParams " + primTest + " prim hole default check fail"); + CheckllSetPrimitiveParamsVector(primCut, api.llList2Vector(primParams, 3), primTest + " prim cut"); + Assert.AreEqual(primHollowCheck, api.llList2Float(primParams, 4), FLOAT_ACCURACY, + "TestllSetPrimitiveParams " + primTest + " prim hollow check fail"); + CheckllSetPrimitiveParamsVector(primTwist, api.llList2Vector(primParams, 5), primTest + " prim twist"); + CheckllSetPrimitiveParamsVector(primHoleSize, api.llList2Vector(primParams, 6), primTest + " prim hole size"); + CheckllSetPrimitiveParamsVector(primShear, api.llList2Vector(primParams, 7), primTest + " prim shear"); + CheckllSetPrimitiveParamsVector(primProfCut, api.llList2Vector(primParams, 8), primTest + " prim profile cut"); + CheckllSetPrimitiveParamsVector(primTaper, api.llList2Vector(primParams, 9), primTest + " prim taper"); + Assert.AreEqual(primRev, api.llList2Float(primParams, 10), FLOAT_ACCURACY, + "TestllSetPrimitiveParams " + primTest + " prim revolutions fail"); + Assert.AreEqual(primRadius, api.llList2Float(primParams, 11), FLOAT_ACCURACY, + "TestllSetPrimitiveParams " + primTest + " prim radius fail"); + Assert.AreEqual(primSkew, api.llList2Float(primParams, 12), FLOAT_ACCURACY, + "TestllSetPrimitiveParams " + primTest + " prim skew fail"); + } + + // Set prim params for a sculpted prim and check results. + public void CheckllSetPrimitiveParams(LSL_Api api, string primTest, + LSL_Types.Vector3 primSize, int primType, string primMap, int primSculptType) + { + // Set the prim params. + api.llSetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, primSize, + ScriptBaseClass.PRIM_TYPE, primType, primMap, primSculptType)); + + // Get params for prim to validate settings. + LSL_Types.list primParams = + api.llGetPrimitiveParams(new LSL_Types.list(ScriptBaseClass.PRIM_SIZE, ScriptBaseClass.PRIM_TYPE)); + + // Validate settings. + CheckllSetPrimitiveParamsVector(primSize, api.llList2Vector(primParams, 0), primTest + " prim size"); + Assert.AreEqual(primType, api.llList2Integer(primParams, 1), + "TestllSetPrimitiveParams " + primTest + " prim type check fail"); + Assert.AreEqual(primMap, (string)api.llList2String(primParams, 2), + "TestllSetPrimitiveParams " + primTest + " prim map check fail"); + Assert.AreEqual(primSculptType, api.llList2Integer(primParams, 3), + "TestllSetPrimitiveParams " + primTest + " prim type scuplt check fail"); + } + + public void CheckllSetPrimitiveParamsVector(LSL_Types.Vector3 vecCheck, LSL_Types.Vector3 vecReturned, string msg) + { + // Check each vector component against expected result. + Assert.AreEqual(vecCheck.x, vecReturned.x, VECTOR_COMPONENT_ACCURACY, + "TestllSetPrimitiveParams " + msg + " vector check fail on x component"); + Assert.AreEqual(vecCheck.y, vecReturned.y, VECTOR_COMPONENT_ACCURACY, + "TestllSetPrimitiveParams " + msg + " vector check fail on y component"); + Assert.AreEqual(vecCheck.z, vecReturned.z, VECTOR_COMPONENT_ACCURACY, + "TestllSetPrimitiveParams " + msg + " vector check fail on z component"); + } + + } + */ +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiTest.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiTest.cs new file mode 100644 index 00000000000..fe763f3c7f7 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiTest.cs @@ -0,0 +1,277 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenMetaverse; +using System; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for LSL_Api + /// + [TestFixture, LongRunning] + public class LSL_ApiTest + { + private const double VECTOR_COMPONENT_ACCURACY = 0.0000005d; + private const double ANGLE_ACCURACY_IN_RADIANS = 1E-6; + private LSL_Api m_lslApi; + + [SetUp] + public void SetUp() + { + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + Scene scene = new SceneHelpers().SetupScene(); + SceneObjectPart part = SceneHelpers.AddSceneObject(scene).RootPart; + + XEngine.XEngine engine = new XEngine.XEngine(); + engine.Initialise(initConfigSource); + engine.AddRegion(scene); + + m_lslApi = new LSL_Api(); + m_lslApi.Initialize(engine, part, null); + } + + [Test] + public void TestllAngleBetween() + { + TestHelpers.InMethod(); + + CheckllAngleBetween(new Vector3(1, 0, 0), 0, 1, 1); + CheckllAngleBetween(new Vector3(1, 0, 0), 90, 1, 1); + CheckllAngleBetween(new Vector3(1, 0, 0), 180, 1, 1); + + CheckllAngleBetween(new Vector3(0, 1, 0), 0, 1, 1); + CheckllAngleBetween(new Vector3(0, 1, 0), 90, 1, 1); + CheckllAngleBetween(new Vector3(0, 1, 0), 180, 1, 1); + + CheckllAngleBetween(new Vector3(0, 0, 1), 0, 1, 1); + CheckllAngleBetween(new Vector3(0, 0, 1), 90, 1, 1); + CheckllAngleBetween(new Vector3(0, 0, 1), 180, 1, 1); + + CheckllAngleBetween(new Vector3(1, 1, 1), 0, 1, 1); + CheckllAngleBetween(new Vector3(1, 1, 1), 90, 1, 1); + CheckllAngleBetween(new Vector3(1, 1, 1), 180, 1, 1); + + CheckllAngleBetween(new Vector3(1, 0, 0), 0, 1.6f, 1.8f); + CheckllAngleBetween(new Vector3(1, 0, 0), 90, 0.3f, 3.9f); + CheckllAngleBetween(new Vector3(1, 0, 0), 180, 8.8f, 7.4f); + + CheckllAngleBetween(new Vector3(0, 1, 0), 0, 9.8f, -9.4f); + CheckllAngleBetween(new Vector3(0, 1, 0), 90, 8.4f, -8.2f); + CheckllAngleBetween(new Vector3(0, 1, 0), 180, 0.4f, -5.8f); + + CheckllAngleBetween(new Vector3(0, 0, 1), 0, -6.8f, 3.4f); + CheckllAngleBetween(new Vector3(0, 0, 1), 90, -3.6f, 5.6f); + CheckllAngleBetween(new Vector3(0, 0, 1), 180, -3.8f, 1.1f); + + CheckllAngleBetween(new Vector3(1, 1, 1), 0, -7.7f, -2.0f); + CheckllAngleBetween(new Vector3(1, 1, 1), 90, -3.0f, -9.1f); + CheckllAngleBetween(new Vector3(1, 1, 1), 180, -7.9f, -8.0f); + } + + private void CheckllAngleBetween(Vector3 axis,float originalAngle, float denorm1, float denorm2) + { + Quaternion rotation1 = Quaternion.CreateFromAxisAngle(axis, 0); + Quaternion rotation2 = Quaternion.CreateFromAxisAngle(axis, ToRadians(originalAngle)); + rotation1 *= denorm1; + rotation2 *= denorm2; + + double deducedAngle = FromLslFloat(m_lslApi.llAngleBetween(ToLslQuaternion(rotation2), ToLslQuaternion(rotation1))); + + Assert.That(deducedAngle, Is.EqualTo(ToRadians(originalAngle)).Within(ANGLE_ACCURACY_IN_RADIANS), "TestllAngleBetween check fail"); + } + + #region Conversions to and from LSL_Types + + private float ToRadians(double degrees) + { + return (float)(Math.PI * degrees / 180); + } + + // private double FromRadians(float radians) + // { + // return radians * 180 / Math.PI; + // } + + private double FromLslFloat(LSL_Types.LSLFloat lslFloat) + { + return lslFloat.value; + } + + // private LSL_Types.LSLFloat ToLslFloat(double value) + // { + // return new LSL_Types.LSLFloat(value); + // } + + // private Quaternion FromLslQuaternion(LSL_Types.Quaternion lslQuaternion) + // { + // return new Quaternion((float)lslQuaternion.x, (float)lslQuaternion.y, (float)lslQuaternion.z, (float)lslQuaternion.s); + // } + + private LSL_Types.Quaternion ToLslQuaternion(Quaternion quaternion) + { + return new LSL_Types.Quaternion(quaternion.X, quaternion.Y, quaternion.Z, quaternion.W); + } + + #endregion + + [Test] + // llRot2Euler test. + public void TestllRot2Euler() + { + TestHelpers.InMethod(); + + // 180, 90 and zero degree rotations. + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.0f, 1.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.707107f, 0.707107f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 1.0f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, 0.0f, 0.707107f, -0.707107f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.707107f, 0.0f, 0.0f, 0.707107f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.5f, -0.5f, 0.5f, 0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, 0.707107f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, 0.5f, -0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(1.0f, 0.0f, 0.0f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.707107f, -0.707107f, 0.0f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -1.0f, 0.0f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, -0.707107f, 0.0f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.707107f, 0.0f, 0.0f, -0.707107f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.5f, -0.5f, -0.5f, -0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, -0.707107f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, -0.5f, 0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, 0.0f, 0.707107f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, 0.5f, 0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, 0.0f, 0.707107f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, 0.5f, 0.5f, -0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.0f, -0.707107f, 0.0f, -0.707107f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, -0.5f, -0.5f, -0.5f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.707107f, 0.0f, -0.707107f, 0.0f)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.5f, 0.5f, -0.5f, 0.5f)); + + // A couple of messy rotations. + CheckllRot2Euler(new LSL_Types.Quaternion(1.0f, 5.651f, -3.1f, 67.023f)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.719188f, -0.408934f, -0.363998f, -0.427841f)); + + // Some deliberately malicious rotations (intended on provoking singularity errors) + // The "f" suffexes are deliberately omitted. + CheckllRot2Euler(new LSL_Types.Quaternion(0.50001f, 0.50001f, 0.50001f, 0.50001f)); + // More malice. The "f" suffixes are deliberately omitted. + CheckllRot2Euler(new LSL_Types.Quaternion(-0.701055, 0.092296, 0.701055, -0.092296)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.183005, -0.683010, 0.183005, 0.683010)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.430460, -0.560982, 0.430460, 0.560982)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.701066, 0.092301, -0.701066, 0.092301)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.183013, -0.683010, 0.183013, 0.683010)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.183005, -0.683014, -0.183005, -0.683014)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.353556, 0.612375, 0.353556, -0.612375)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.353554, -0.612385, -0.353554, 0.612385)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.560989, 0.430450, 0.560989, -0.430450)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.183013, 0.683009, -0.183013, 0.683009)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.430457, -0.560985, -0.430457, 0.560985)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.353552, 0.612360, -0.353552, -0.612360)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.499991, 0.500003, 0.499991, -0.500003)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.353555, -0.612385, -0.353555, -0.612385)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.701066, -0.092301, -0.701066, 0.092301)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.499991, 0.500007, 0.499991, -0.500007)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.683002, 0.183016, -0.683002, 0.183016)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.430458, 0.560982, 0.430458, 0.560982)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.499991, -0.500003, -0.499991, 0.500003)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.183009, 0.683011, -0.183009, 0.683011)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.560975, -0.430457, 0.560975, -0.430457)); + CheckllRot2Euler(new LSL_Types.Quaternion(0.701055, 0.092300, 0.701055, 0.092300)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.560990, 0.430459, -0.560990, 0.430459)); + CheckllRot2Euler(new LSL_Types.Quaternion(-0.092302, -0.701059, -0.092302, -0.701059)); + } + + /// + /// Check an llRot2Euler conversion. + /// + /// + /// Testing Rot2Euler this way instead of comparing against expected angles because + /// 1. There are several ways to get to the original Quaternion. For example a rotation + /// of PI and -PI will give the same result. But PI and -PI aren't equal. + /// 2. This method checks to see if the calculated angles from a quaternion can be used + /// to create a new quaternion to produce the same rotation. + /// However, can't compare the newly calculated quaternion against the original because + /// once again, there are multiple quaternions that give the same result. For instance + /// == <-X, -Y, -Z, -S>. Additionally, the magnitude of S can be changed + /// and will still result in the same rotation if the values for X, Y, Z are also changed + /// to compensate. + /// However, if two quaternions represent the same rotation, then multiplying the first + /// quaternion by the conjugate of the second, will give a third quaternion representing + /// a zero rotation. This can be tested for by looking at the X, Y, Z values which should + /// be zero. + /// + /// + private void CheckllRot2Euler(LSL_Types.Quaternion rot) + { + // Call LSL function to convert quaternion rotaion to euler radians. + LSL_Types.Vector3 eulerCalc = m_lslApi.llRot2Euler(rot); + // Now use the euler radians to recalculate a new quaternion rotation + LSL_Types.Quaternion newRot = m_lslApi.llEuler2Rot(eulerCalc); + // Multiple original quaternion by conjugate of quaternion calculated with angles. + LSL_Types.Quaternion check = rot * new LSL_Types.Quaternion(-newRot.x, -newRot.y, -newRot.z, newRot.s); + + Assert.AreEqual(0.0, check.x, VECTOR_COMPONENT_ACCURACY, "TestllRot2Euler X bounds check fail"); + Assert.AreEqual(0.0, check.y, VECTOR_COMPONENT_ACCURACY, "TestllRot2Euler Y bounds check fail"); + Assert.AreEqual(0.0, check.z, VECTOR_COMPONENT_ACCURACY, "TestllRot2Euler Z bounds check fail"); + } + + [Test] + public void TestllVecNorm() + { + TestHelpers.InMethod(); + + // Check special case for normalizing zero vector. + CheckllVecNorm(new LSL_Types.Vector3(0.0d, 0.0d, 0.0d), new LSL_Types.Vector3(0.0d, 0.0d, 0.0d)); + // Check various vectors. + CheckllVecNorm(new LSL_Types.Vector3(10.0d, 25.0d, 0.0d), new LSL_Types.Vector3(0.371391d, 0.928477d, 0.0d)); + CheckllVecNorm(new LSL_Types.Vector3(1.0d, 0.0d, 0.0d), new LSL_Types.Vector3(1.0d, 0.0d, 0.0d)); + CheckllVecNorm(new LSL_Types.Vector3(-90.0d, 55.0d, 2.0d), new LSL_Types.Vector3(-0.853128d, 0.521356d, 0.018958d)); + CheckllVecNorm(new LSL_Types.Vector3(255.0d, 255.0d, 255.0d), new LSL_Types.Vector3(0.577350d, 0.577350d, 0.577350d)); + } + + public void CheckllVecNorm(LSL_Types.Vector3 vec, LSL_Types.Vector3 vecNormCheck) + { + // Call LSL function to normalize the vector. + LSL_Types.Vector3 vecNorm = m_lslApi.llVecNorm(vec); + // Check each vector component against expected result. + Assert.AreEqual(vecNorm.x, vecNormCheck.x, VECTOR_COMPONENT_ACCURACY, "TestllVecNorm vector check fail on x component"); + Assert.AreEqual(vecNorm.y, vecNormCheck.y, VECTOR_COMPONENT_ACCURACY, "TestllVecNorm vector check fail on y component"); + Assert.AreEqual(vecNorm.z, vecNormCheck.z, VECTOR_COMPONENT_ACCURACY, "TestllVecNorm vector check fail on z component"); + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiUserTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiUserTests.cs new file mode 100644 index 00000000000..83525ae0519 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_ApiUserTests.cs @@ -0,0 +1,156 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Threading; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + [TestFixture] + public class LSL_ApiUserTests : OpenSimTestCase + { + private Scene m_scene; + private MockScriptEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_engine = new MockScriptEngine(); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, m_engine); + } + + [Test] + public void TestLlRequestAgentDataOnline() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(m_scene, userId); + + SceneObjectPart part = SceneHelpers.AddSceneObject(m_scene).RootPart; + TaskInventoryItem scriptItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, part); + + LSL_Api apiGrp1 = new LSL_Api(); + apiGrp1.Initialize(m_engine, part, scriptItem); + + // Initially long timeout to test cache + apiGrp1.LlRequestAgentDataCacheTimeoutMs = 20000; + + // Offline test + { + apiGrp1.llRequestAgentData(userId.ToString(), ScriptBaseClass.DATA_ONLINE); + + Assert.That(m_engine.PostedEvents.ContainsKey(scriptItem.ItemID)); + + List events = m_engine.PostedEvents[scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("dataserver")); + + string data = eventParams.Params[1].ToString(); + Assert.AreEqual(0, int.Parse(data)); + + m_engine.PostedEvents.Clear(); + } + + // Online test. Should get the 'wrong' result because of caching. + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, ua1); + + { + apiGrp1.llRequestAgentData(userId.ToString(), ScriptBaseClass.DATA_ONLINE); + + Assert.That(m_engine.PostedEvents.ContainsKey(scriptItem.ItemID)); + + List events = m_engine.PostedEvents[scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("dataserver")); + + string data = eventParams.Params[1].ToString(); + Assert.AreEqual(0, int.Parse(data)); + + m_engine.PostedEvents.Clear(); + } + + apiGrp1.LlRequestAgentDataCacheTimeoutMs = 20; + + // Make absolutely sure that we should trigger cache timeout. + Thread.Sleep(apiGrp1.LlRequestAgentDataCacheTimeoutMs + 50); + + { + apiGrp1.llRequestAgentData(userId.ToString(), ScriptBaseClass.DATA_ONLINE); + + Assert.That(m_engine.PostedEvents.ContainsKey(scriptItem.ItemID)); + + List events = m_engine.PostedEvents[scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("dataserver")); + + string data = eventParams.Params[1].ToString(); + Assert.AreEqual(1, int.Parse(data)); + + m_engine.PostedEvents.Clear(); + } + + m_scene.CloseAgent(userId, false); + + Thread.Sleep(apiGrp1.LlRequestAgentDataCacheTimeoutMs + 50); + + { + apiGrp1.llRequestAgentData(userId.ToString(), ScriptBaseClass.DATA_ONLINE); + + Assert.That(m_engine.PostedEvents.ContainsKey(scriptItem.ItemID)); + + List events = m_engine.PostedEvents[scriptItem.ItemID]; + Assert.That(events.Count, Is.EqualTo(1)); + EventParams eventParams = events[0]; + Assert.That(eventParams.EventName, Is.EqualTo("dataserver")); + + string data = eventParams.Params[1].ToString(); + Assert.AreEqual(0, int.Parse(data)); + + m_engine.PostedEvents.Clear(); + } + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLFloat.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLFloat.cs new file mode 100644 index 00000000000..f81e0af04d8 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLFloat.cs @@ -0,0 +1,661 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + [TestFixture] + public class LSL_TypesTestLSLFloat : OpenSimTestCase + { + // Used for testing equality of two floats. + private double _lowPrecisionTolerance = 0.000001; + + private Dictionary m_intDoubleSet; + private Dictionary m_doubleDoubleSet; + private Dictionary m_doubleIntSet; + private Dictionary m_doubleUintSet; + private Dictionary m_stringDoubleSet; + private Dictionary m_doubleStringSet; + private List m_intList; + private List m_doubleList; + + /// + /// Sets up dictionaries and arrays used in the tests. + /// + [OneTimeSetUp] + public void SetUpDataSets() + { + m_intDoubleSet = new Dictionary(); + m_intDoubleSet.Add(2, 2.0); + m_intDoubleSet.Add(-2, -2.0); + m_intDoubleSet.Add(0, 0.0); + m_intDoubleSet.Add(1, 1.0); + m_intDoubleSet.Add(-1, -1.0); + m_intDoubleSet.Add(999999999, 999999999.0); + m_intDoubleSet.Add(-99999999, -99999999.0); + + m_doubleDoubleSet = new Dictionary(); + m_doubleDoubleSet.Add(2.0, 2.0); + m_doubleDoubleSet.Add(-2.0, -2.0); + m_doubleDoubleSet.Add(0.0, 0.0); + m_doubleDoubleSet.Add(1.0, 1.0); + m_doubleDoubleSet.Add(-1.0, -1.0); + m_doubleDoubleSet.Add(999999999.0, 999999999.0); + m_doubleDoubleSet.Add(-99999999.0, -99999999.0); + m_doubleDoubleSet.Add(0.5, 0.5); + m_doubleDoubleSet.Add(0.0005, 0.0005); + m_doubleDoubleSet.Add(0.6805, 0.6805); + m_doubleDoubleSet.Add(-0.5, -0.5); + m_doubleDoubleSet.Add(-0.0005, -0.0005); + m_doubleDoubleSet.Add(-0.6805, -0.6805); + m_doubleDoubleSet.Add(548.5, 548.5); + m_doubleDoubleSet.Add(2.0005, 2.0005); + m_doubleDoubleSet.Add(349485435.6805, 349485435.6805); + m_doubleDoubleSet.Add(-548.5, -548.5); + m_doubleDoubleSet.Add(-2.0005, -2.0005); + m_doubleDoubleSet.Add(-349485435.6805, -349485435.6805); + + m_doubleIntSet = new Dictionary(); + m_doubleIntSet.Add(2.0, 2); + m_doubleIntSet.Add(-2.0, -2); + m_doubleIntSet.Add(0.0, 0); + m_doubleIntSet.Add(1.0, 1); + m_doubleIntSet.Add(-1.0, -1); + m_doubleIntSet.Add(999999999.0, 999999999); + m_doubleIntSet.Add(-99999999.0, -99999999); + m_doubleIntSet.Add(0.5, 0); + m_doubleIntSet.Add(0.0005, 0); + m_doubleIntSet.Add(0.6805, 0); + m_doubleIntSet.Add(-0.5, 0); + m_doubleIntSet.Add(-0.0005, 0); + m_doubleIntSet.Add(-0.6805, 0); + m_doubleIntSet.Add(548.5, 548); + m_doubleIntSet.Add(2.0005, 2); + m_doubleIntSet.Add(349485435.6805, 349485435); + m_doubleIntSet.Add(-548.5, -548); + m_doubleIntSet.Add(-2.0005, -2); + m_doubleIntSet.Add(-349485435.6805, -349485435); + + m_doubleUintSet = new Dictionary(); + m_doubleUintSet.Add(2.0, 2); + m_doubleUintSet.Add(-2.0, 2); + m_doubleUintSet.Add(0.0, 0); + m_doubleUintSet.Add(1.0, 1); + m_doubleUintSet.Add(-1.0, 1); + m_doubleUintSet.Add(999999999.0, 999999999); + m_doubleUintSet.Add(-99999999.0, 99999999); + m_doubleUintSet.Add(0.5, 0); + m_doubleUintSet.Add(0.0005, 0); + m_doubleUintSet.Add(0.6805, 0); + m_doubleUintSet.Add(-0.5, 0); + m_doubleUintSet.Add(-0.0005, 0); + m_doubleUintSet.Add(-0.6805, 0); + m_doubleUintSet.Add(548.5, 548); + m_doubleUintSet.Add(2.0005, 2); + m_doubleUintSet.Add(349485435.6805, 349485435); + m_doubleUintSet.Add(-548.5, 548); + m_doubleUintSet.Add(-2.0005, 2); + m_doubleUintSet.Add(-349485435.6805, 349485435); + + m_stringDoubleSet = new Dictionary(); + m_stringDoubleSet.Add("2", 2.0); + m_stringDoubleSet.Add("-2", -2.0); + m_stringDoubleSet.Add("1", 1.0); + m_stringDoubleSet.Add("-1", -1.0); + m_stringDoubleSet.Add("0", 0.0); + m_stringDoubleSet.Add("999999999.0", 999999999.0); + m_stringDoubleSet.Add("-99999999.0", -99999999.0); + m_stringDoubleSet.Add("0.5", 0.5); + m_stringDoubleSet.Add("0.0005", 0.0005); + m_stringDoubleSet.Add("0.6805", 0.6805); + m_stringDoubleSet.Add("-0.5", -0.5); + m_stringDoubleSet.Add("-0.0005", -0.0005); + m_stringDoubleSet.Add("-0.6805", -0.6805); + m_stringDoubleSet.Add("548.5", 548.5); + m_stringDoubleSet.Add("2.0005", 2.0005); + m_stringDoubleSet.Add("349485435.6805", 349485435.6805); + m_stringDoubleSet.Add("-548.5", -548.5); + m_stringDoubleSet.Add("-2.0005", -2.0005); + m_stringDoubleSet.Add("-349485435.6805", -349485435.6805); + // some oddball combinations and exponents + m_stringDoubleSet.Add("", 0.0); + m_stringDoubleSet.Add("1.0E+5", 100000.0); + m_stringDoubleSet.Add("-1.0E+5", -100000.0); + m_stringDoubleSet.Add("-1E+5", -100000.0); + m_stringDoubleSet.Add("-1.E+5", -100000.0); + m_stringDoubleSet.Add("-1.E+5.0", -100000.0); + m_stringDoubleSet.Add("1ef", 1.0); + m_stringDoubleSet.Add("e10", 0.0); + m_stringDoubleSet.Add("1.e0.0", 1.0); + + m_doubleStringSet = new Dictionary(); + m_doubleStringSet.Add(2.0, "2.000000"); + m_doubleStringSet.Add(-2.0, "-2.000000"); + m_doubleStringSet.Add(1.0, "1.000000"); + m_doubleStringSet.Add(-1.0, "-1.000000"); + m_doubleStringSet.Add(0.0, "0.000000"); + m_doubleStringSet.Add(999999999.0, "999999999.000000"); + m_doubleStringSet.Add(-99999999.0, "-99999999.000000"); + m_doubleStringSet.Add(0.5, "0.500000"); + m_doubleStringSet.Add(0.0005, "0.000500"); + m_doubleStringSet.Add(0.6805, "0.680500"); + m_doubleStringSet.Add(-0.5, "-0.500000"); + m_doubleStringSet.Add(-0.0005, "-0.000500"); + m_doubleStringSet.Add(-0.6805, "-0.680500"); + m_doubleStringSet.Add(548.5, "548.500000"); + m_doubleStringSet.Add(2.0005, "2.000500"); + m_doubleStringSet.Add(349485435.6805, "349485435.680500"); + m_doubleStringSet.Add(-548.5, "-548.500000"); + m_doubleStringSet.Add(-2.0005, "-2.000500"); + m_doubleStringSet.Add(-349485435.6805, "-349485435.680500"); + + m_doubleList = new List(); + m_doubleList.Add(2.0); + m_doubleList.Add(-2.0); + m_doubleList.Add(1.0); + m_doubleList.Add(-1.0); + m_doubleList.Add(999999999.0); + m_doubleList.Add(-99999999.0); + m_doubleList.Add(0.5); + m_doubleList.Add(0.0005); + m_doubleList.Add(0.6805); + m_doubleList.Add(-0.5); + m_doubleList.Add(-0.0005); + m_doubleList.Add(-0.6805); + m_doubleList.Add(548.5); + m_doubleList.Add(2.0005); + m_doubleList.Add(349485435.6805); + m_doubleList.Add(-548.5); + m_doubleList.Add(-2.0005); + m_doubleList.Add(-349485435.6805); + + m_intList = new List(); + m_intList.Add(2); + m_intList.Add(-2); + m_intList.Add(0); + m_intList.Add(1); + m_intList.Add(-1); + m_intList.Add(999999999); + m_intList.Add(-99999999); + } + + /// + /// Tests constructing a LSLFloat from an integer. + /// + [Test] + public void TestConstructFromInt() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (KeyValuePair number in m_intDoubleSet) + { + testFloat = new LSL_Types.LSLFloat(number.Key); + Assert.That(testFloat.value, new DoubleToleranceConstraint(number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests constructing a LSLFloat from a double. + /// + [Test] + public void TestConstructFromDouble() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (KeyValuePair number in m_doubleDoubleSet) + { + testFloat = new LSL_Types.LSLFloat(number.Key); + Assert.That(testFloat.value, new DoubleToleranceConstraint(number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLFloat is correctly cast explicitly to integer. + /// + [Test] + public void TestExplicitCastLSLFloatToInt() + { + TestHelpers.InMethod(); + + int testNumber; + + foreach (KeyValuePair number in m_doubleIntSet) + { + testNumber = (int) new LSL_Types.LSLFloat(number.Key); + Assert.AreEqual(number.Value, testNumber, "Converting double " + number.Key + ", expecting int " + number.Value); + } + } + + /// + /// Tests LSLFloat is correctly cast explicitly to unsigned integer. + /// + [Test] + public void TestExplicitCastLSLFloatToUint() + { + TestHelpers.InMethod(); + + uint testNumber; + + foreach (KeyValuePair number in m_doubleUintSet) + { + testNumber = (uint) new LSL_Types.LSLFloat(number.Key); + Assert.AreEqual(number.Value, testNumber, "Converting double " + number.Key + ", expecting uint " + number.Value); + } + } + + /// + /// Tests LSLFloat is correctly cast implicitly to Boolean if non-zero. + /// + [Test] + public void TestImplicitCastLSLFloatToBooleanTrue() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + bool testBool; + + foreach (double number in m_doubleList) + { + testFloat = new LSL_Types.LSLFloat(number); + testBool = testFloat; + + Assert.IsTrue(testBool); + } + } + + /// + /// Tests LSLFloat is correctly cast implicitly to Boolean if zero. + /// + [Test] + public void TestImplicitCastLSLFloatToBooleanFalse() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat = new LSL_Types.LSLFloat(0.0); + bool testBool = testFloat; + + Assert.IsFalse(testBool); + } + + /// + /// Tests integer is correctly cast implicitly to LSLFloat. + /// + [Test] + public void TestImplicitCastIntToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (int number in m_intList) + { + testFloat = number; + Assert.That(testFloat.value, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLInteger is correctly cast implicitly to LSLFloat. + /// + [Test] + public void TestImplicitCastLSLIntegerToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (int number in m_intList) + { + testFloat = new LSL_Types.LSLInteger(number); + Assert.That(testFloat.value, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLInteger is correctly cast explicitly to LSLFloat. + /// + [Test] + public void TestExplicitCastLSLIntegerToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (int number in m_intList) + { + testFloat = (LSL_Types.LSLFloat) new LSL_Types.LSLInteger(number); + Assert.That(testFloat.value, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + } + } + + /// + /// Tests string is correctly cast explicitly to LSLFloat. + /// + [Test] + public void TestExplicitCastStringToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (KeyValuePair number in m_stringDoubleSet) + { + testFloat = (LSL_Types.LSLFloat) number.Key; + Assert.That(testFloat.value, new DoubleToleranceConstraint(number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLString is correctly cast implicitly to LSLFloat. + /// + [Test] + public void TestExplicitCastLSLStringToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (KeyValuePair number in m_stringDoubleSet) + { + testFloat = (LSL_Types.LSLFloat) new LSL_Types.LSLString(number.Key); + Assert.That(testFloat.value, new DoubleToleranceConstraint(number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests double is correctly cast implicitly to LSLFloat. + /// + [Test] + public void TestImplicitCastDoubleToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (double number in m_doubleList) + { + testFloat = number; + Assert.That(testFloat.value, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLFloat is correctly cast implicitly to double. + /// + [Test] + public void TestImplicitCastLSLFloatToDouble() + { + TestHelpers.InMethod(); + + double testNumber; + LSL_Types.LSLFloat testFloat; + + foreach (double number in m_doubleList) + { + testFloat = new LSL_Types.LSLFloat(number); + testNumber = testFloat; + + Assert.That(testNumber, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLFloat is correctly cast explicitly to float + /// + [Test] + public void TestExplicitCastLSLFloatToFloat() + { + TestHelpers.InMethod(); + + float testFloat; + float numberAsFloat; + LSL_Types.LSLFloat testLSLFloat; + + foreach (double number in m_doubleList) + { + testLSLFloat = new LSL_Types.LSLFloat(number); + numberAsFloat = (float)number; + testFloat = (float)testLSLFloat; + + Assert.That((double)testFloat, new DoubleToleranceConstraint((double)numberAsFloat, _lowPrecisionTolerance)); + } + } + + /// + /// Tests the equality (==) operator. + /// + [Test] + public void TestEqualsOperator() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloatA, testFloatB; + + foreach (double number in m_doubleList) + { + testFloatA = new LSL_Types.LSLFloat(number); + testFloatB = new LSL_Types.LSLFloat(number); + Assert.IsTrue(testFloatA == testFloatB); + + testFloatB = new LSL_Types.LSLFloat(number + 1.0); + Assert.IsFalse(testFloatA == testFloatB); + } + } + + /// + /// Tests the inequality (!=) operator. + /// + [Test] + public void TestNotEqualOperator() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloatA, testFloatB; + + foreach (double number in m_doubleList) + { + testFloatA = new LSL_Types.LSLFloat(number); + testFloatB = new LSL_Types.LSLFloat(number + 1.0); + Assert.IsTrue(testFloatA != testFloatB); + + testFloatB = new LSL_Types.LSLFloat(number); + Assert.IsFalse(testFloatA != testFloatB); + } + } + + /// + /// Tests the increment operator. + /// + [Test] + public void TestIncrementOperator() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + double testNumber; + + foreach (double number in m_doubleList) + { + testFloat = new LSL_Types.LSLFloat(number); + + testNumber = testFloat++; + Assert.That(testNumber, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + + testNumber = testFloat; + Assert.That(testNumber, new DoubleToleranceConstraint(number + 1.0, _lowPrecisionTolerance)); + + testNumber = ++testFloat; + Assert.That(testNumber, new DoubleToleranceConstraint(number + 2.0, _lowPrecisionTolerance)); + } + } + + /// + /// Tests the decrement operator. + /// + [Test] + public void TestDecrementOperator() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + double testNumber; + + foreach (double number in m_doubleList) + { + testFloat = new LSL_Types.LSLFloat(number); + + testNumber = testFloat--; + Assert.That(testNumber, new DoubleToleranceConstraint(number, _lowPrecisionTolerance)); + + testNumber = testFloat; + Assert.That(testNumber, new DoubleToleranceConstraint(number - 1.0, _lowPrecisionTolerance)); + + testNumber = --testFloat; + Assert.That(testNumber, new DoubleToleranceConstraint(number - 2.0, _lowPrecisionTolerance)); + } + } + + /// + /// Tests LSLFloat.ToString(). + /// + [Test] + public void TestToString() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + foreach (KeyValuePair number in m_doubleStringSet) + { + testFloat = new LSL_Types.LSLFloat(number.Key); + Assert.AreEqual(number.Value, testFloat.ToString()); + } + } + + /// + /// Tests addition of two LSLFloats. + /// + [Test] + public void TestAddTwoLSLFloats() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testResult; + + foreach (KeyValuePair number in m_doubleDoubleSet) + { + testResult = new LSL_Types.LSLFloat(number.Key) + new LSL_Types.LSLFloat(number.Value); + Assert.That(testResult.value, new DoubleToleranceConstraint(number.Key + number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests subtraction of two LSLFloats. + /// + [Test] + public void TestSubtractTwoLSLFloats() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testResult; + + foreach (KeyValuePair number in m_doubleDoubleSet) + { + testResult = new LSL_Types.LSLFloat(number.Key) - new LSL_Types.LSLFloat(number.Value); + Assert.That(testResult.value, new DoubleToleranceConstraint(number.Key - number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests multiplication of two LSLFloats. + /// + [Test] + public void TestMultiplyTwoLSLFloats() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testResult; + + foreach (KeyValuePair number in m_doubleDoubleSet) + { + testResult = new LSL_Types.LSLFloat(number.Key) * new LSL_Types.LSLFloat(number.Value); + Assert.That(testResult.value, new DoubleToleranceConstraint(number.Key * number.Value, _lowPrecisionTolerance)); + } + } + + /// + /// Tests division of two LSLFloats. + /// + [Test] + public void TestDivideTwoLSLFloats() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testResult; + + foreach (KeyValuePair number in m_doubleDoubleSet) + { + if (number.Value != 0.0) // Let's avoid divide by zero. + { + testResult = new LSL_Types.LSLFloat(number.Key) / new LSL_Types.LSLFloat(number.Value); + Assert.That(testResult.value, new DoubleToleranceConstraint(number.Key / number.Value, _lowPrecisionTolerance)); + } + } + } + + /// + /// Tests boolean correctly cast implicitly to LSLFloat. + /// + [Test] + public void TestImplicitCastBooleanToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testFloat; + + testFloat = (1 == 0); + Assert.That(testFloat.value, new DoubleToleranceConstraint(0.0, _lowPrecisionTolerance)); + + testFloat = (1 == 1); + Assert.That(testFloat.value, new DoubleToleranceConstraint(1.0, _lowPrecisionTolerance)); + + testFloat = false; + Assert.That(testFloat.value, new DoubleToleranceConstraint(0.0, _lowPrecisionTolerance)); + + testFloat = true; + Assert.That(testFloat.value, new DoubleToleranceConstraint(1.0, _lowPrecisionTolerance)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLInteger.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLInteger.cs new file mode 100644 index 00000000000..41b5bcc0b8f --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLInteger.cs @@ -0,0 +1,150 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + [TestFixture] + public class LSL_TypesTestLSLInteger : OpenSimTestCase + { + private Dictionary m_doubleIntSet; + private Dictionary m_stringIntSet; + + /// + /// Sets up dictionaries and arrays used in the tests. + /// + [OneTimeSetUp] + public void SetUpDataSets() + { + m_doubleIntSet = new Dictionary(); + m_doubleIntSet.Add(2.0, 2); + m_doubleIntSet.Add(-2.0, -2); + m_doubleIntSet.Add(0.0, 0); + m_doubleIntSet.Add(1.0, 1); + m_doubleIntSet.Add(-1.0, -1); + m_doubleIntSet.Add(999999999.0, 999999999); + m_doubleIntSet.Add(-99999999.0, -99999999); + + m_stringIntSet = new Dictionary(); + m_stringIntSet.Add("2", 2); + m_stringIntSet.Add("-2", -2); + m_stringIntSet.Add("0", 0); + m_stringIntSet.Add("1", 1); + m_stringIntSet.Add("-1", -1); + m_stringIntSet.Add("123.9", 123); + m_stringIntSet.Add("999999999", 999999999); + m_stringIntSet.Add("-99999999", -99999999); + m_stringIntSet.Add("", 0); + m_stringIntSet.Add("aa", 0); + m_stringIntSet.Add("56foo", 56); + m_stringIntSet.Add("42", 42); + m_stringIntSet.Add("42 is the answer", 42); + m_stringIntSet.Add(" 42", 42); + m_stringIntSet.Add("42,123,456", 42); + m_stringIntSet.Add("0xff", 255); + m_stringIntSet.Add("12345678900000", -1); + } + + /// + /// Tests LSLFloat is correctly cast explicitly to LSLInteger. + /// + [Test] + public void TestExplicitCastLSLFloatToLSLInteger() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testInteger; + + foreach (KeyValuePair number in m_doubleIntSet) + { + testInteger = (LSL_Types.LSLInteger) new LSL_Types.LSLFloat(number.Key); + Assert.AreEqual(testInteger.value, number.Value); + } + } + + /// + /// Tests string is correctly cast explicitly to LSLInteger. + /// + [Test] + public void TestExplicitCastStringToLSLInteger() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testInteger; + + foreach (KeyValuePair number in m_stringIntSet) + { + testInteger = (LSL_Types.LSLInteger) number.Key; + Assert.AreEqual(testInteger.value, number.Value); + } + } + + /// + /// Tests LSLString is correctly cast explicitly to LSLInteger. + /// + [Test] + public void TestExplicitCastLSLStringToLSLInteger() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testInteger; + + foreach (KeyValuePair number in m_stringIntSet) + { + testInteger = (LSL_Types.LSLInteger) new LSL_Types.LSLString(number.Key); + Assert.AreEqual(testInteger.value, number.Value); + } + } + + /// + /// Tests boolean correctly cast implicitly to LSLInteger. + /// + [Test] + public void TestImplicitCastBooleanToLSLInteger() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testInteger; + + testInteger = (1 == 0); + Assert.AreEqual(0, testInteger.value); + + testInteger = (1 == 1); + Assert.AreEqual(1, testInteger.value); + + testInteger = false; + Assert.AreEqual(0, testInteger.value); + + testInteger = true; + Assert.AreEqual(1, testInteger.value); + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLString.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLString.cs new file mode 100644 index 00000000000..70c1a381c06 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestLSLString.cs @@ -0,0 +1,144 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + [TestFixture] + public class LSL_TypesTestLSLString : OpenSimTestCase + { + private Dictionary m_doubleStringSet; + + /// + /// Sets up dictionaries and arrays used in the tests. + /// + [OneTimeSetUp] + public void SetUpDataSets() + { + m_doubleStringSet = new Dictionary(); + m_doubleStringSet.Add(2, "2.000000"); + m_doubleStringSet.Add(-2, "-2.000000"); + m_doubleStringSet.Add(0, "0.000000"); + m_doubleStringSet.Add(1, "1.000000"); + m_doubleStringSet.Add(-1, "-1.000000"); + m_doubleStringSet.Add(999999999, "999999999.000000"); + m_doubleStringSet.Add(-99999999, "-99999999.000000"); + m_doubleStringSet.Add(0.5, "0.500000"); + m_doubleStringSet.Add(0.0005, "0.000500"); + m_doubleStringSet.Add(0.6805, "0.680500"); + m_doubleStringSet.Add(-0.5, "-0.500000"); + m_doubleStringSet.Add(-0.0005, "-0.000500"); + m_doubleStringSet.Add(-0.6805, "-0.680500"); + m_doubleStringSet.Add(548.5, "548.500000"); + m_doubleStringSet.Add(2.0005, "2.000500"); + m_doubleStringSet.Add(349485435.6805, "349485435.680500"); + m_doubleStringSet.Add(-548.5, "-548.500000"); + m_doubleStringSet.Add(-2.0005, "-2.000500"); + m_doubleStringSet.Add(-349485435.6805, "-349485435.680500"); + } + + /// + /// Tests constructing a LSLString from an LSLFloat. + /// + [Test] + public void TestConstructFromLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLString testString; + + foreach (KeyValuePair number in m_doubleStringSet) + { + testString = new LSL_Types.LSLString(new LSL_Types.LSLFloat(number.Key)); + Assert.AreEqual(number.Value, testString.m_string); + } + } + + /// + /// Tests constructing a LSLString from an LSLFloat. + /// + [Test] + public void TestExplicitCastLSLFloatToLSLString() + { + TestHelpers.InMethod(); + + LSL_Types.LSLString testString; + + foreach (KeyValuePair number in m_doubleStringSet) + { + testString = (LSL_Types.LSLString) new LSL_Types.LSLFloat(number.Key); + Assert.AreEqual(number.Value, testString.m_string); + } + } + + /// + /// Test constructing a Quaternion from a string. + /// + [Test] + public void TestExplicitCastLSLStringToQuaternion() + { + TestHelpers.InMethod(); + + string quaternionString = "<0.00000, 0.70711, 0.00000, 0.70711>"; + LSL_Types.LSLString quaternionLSLString = new LSL_Types.LSLString(quaternionString); + + LSL_Types.Quaternion expectedQuaternion = new LSL_Types.Quaternion(0.0, 0.70711, 0.0, 0.70711); + LSL_Types.Quaternion stringQuaternion = (LSL_Types.Quaternion) quaternionString; + LSL_Types.Quaternion LSLStringQuaternion = (LSL_Types.Quaternion) quaternionLSLString; + + Assert.AreEqual(expectedQuaternion, stringQuaternion); + Assert.AreEqual(expectedQuaternion, LSLStringQuaternion); + } + + /// + /// Tests boolean correctly cast explicitly to LSLString. + /// + [Test] + public void TestImplicitCastBooleanToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLString testString; + + testString = (LSL_Types.LSLString) (1 == 0); + Assert.AreEqual("0", testString.m_string); + + testString = (LSL_Types.LSLString) (1 == 1); + Assert.AreEqual("1", testString.m_string); + + testString = (LSL_Types.LSLString) false; + Assert.AreEqual("0", testString.m_string); + + testString = (LSL_Types.LSLString) true; + Assert.AreEqual("1", testString.m_string); + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestList.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestList.cs new file mode 100644 index 00000000000..fe2113bd561 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestList.cs @@ -0,0 +1,295 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests the LSL_Types.list class. + /// + [TestFixture] + public class LSL_TypesTestList : OpenSimTestCase + { + /// + /// Tests concatenating a string to a list. + /// + [Test] + public void TestConcatenateString() + { + TestHelpers.InMethod(); + + LSL_Types.list testList = new LSL_Types.list(new LSL_Types.LSLInteger(1), new LSL_Types.LSLInteger('a'), new LSL_Types.LSLString("test")); + testList += new LSL_Types.LSLString("addition"); + + Assert.AreEqual(4, testList.Length); + Assert.AreEqual(new LSL_Types.LSLString("addition"), testList.Data[3]); + Assert.AreEqual(typeof(LSL_Types.LSLString), testList.Data[3].GetType()); + + LSL_Types.list secondTestList = testList + new LSL_Types.LSLString("more"); + + Assert.AreEqual(5, secondTestList.Length); + Assert.AreEqual(new LSL_Types.LSLString("more"), secondTestList.Data[4]); + Assert.AreEqual(typeof(LSL_Types.LSLString), secondTestList.Data[4].GetType()); + } + + /// + /// Tests concatenating an integer to a list. + /// + [Test] + public void TestConcatenateInteger() + { + TestHelpers.InMethod(); + + LSL_Types.list testList = new LSL_Types.list(new LSL_Types.LSLInteger(1), new LSL_Types.LSLInteger('a'), new LSL_Types.LSLString("test")); + testList += new LSL_Types.LSLInteger(20); + + Assert.AreEqual(4, testList.Length); + Assert.AreEqual(new LSL_Types.LSLInteger(20), testList.Data[3]); + Assert.AreEqual(typeof(LSL_Types.LSLInteger), testList.Data[3].GetType()); + + LSL_Types.list secondTestList = testList + new LSL_Types.LSLInteger(2); + + Assert.AreEqual(5, secondTestList.Length); + Assert.AreEqual(new LSL_Types.LSLInteger(2), secondTestList.Data[4]); + Assert.AreEqual(typeof(LSL_Types.LSLInteger), secondTestList.Data[4].GetType()); + } + + /// + /// Tests concatenating a float to a list. + /// + [Test] + public void TestConcatenateDouble() + { + TestHelpers.InMethod(); + + LSL_Types.list testList = new LSL_Types.list(new LSL_Types.LSLInteger(1), new LSL_Types.LSLInteger('a'), new LSL_Types.LSLString("test")); + testList += new LSL_Types.LSLFloat(2.0f); + + Assert.AreEqual(4, testList.Length); + Assert.AreEqual(new LSL_Types.LSLFloat(2.0f), testList.Data[3]); + Assert.AreEqual(typeof(LSL_Types.LSLFloat), testList.Data[3].GetType()); + + LSL_Types.list secondTestList = testList + new LSL_Types.LSLFloat(0.04f); + + Assert.AreEqual(5, secondTestList.Length); + Assert.AreEqual(new LSL_Types.LSLFloat(0.04f), secondTestList.Data[4]); + Assert.AreEqual(typeof(LSL_Types.LSLFloat), secondTestList.Data[4].GetType()); + } + + /// + /// Tests casting LSLInteger item to LSLInteger. + /// + [Test] + public void TestCastLSLIntegerItemToLSLInteger() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testValue = new LSL_Types.LSLInteger(123); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, (LSL_Types.LSLInteger)testList.Data[0]); + } + + /// + /// Tests casting LSLFloat item to LSLFloat. + /// + [Test] + public void TestCastLSLFloatItemToLSLFloat() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testValue = new LSL_Types.LSLFloat(123.45678987); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, (LSL_Types.LSLFloat)testList.Data[0]); + } + + /// + /// Tests casting LSLString item to LSLString. + /// + [Test] + public void TestCastLSLStringItemToLSLString() + { + TestHelpers.InMethod(); + + LSL_Types.LSLString testValue = new LSL_Types.LSLString("hello there"); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, (LSL_Types.LSLString)testList.Data[0]); + } + + /// + /// Tests casting Vector3 item to Vector3. + /// + [Test] + public void TestCastVector3ItemToVector3() + { + TestHelpers.InMethod(); + + LSL_Types.Vector3 testValue = new LSL_Types.Vector3(12.34, 56.987654, 0.00987); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, (LSL_Types.Vector3)testList.Data[0]); + } + /// + /// Tests casting Quaternion item to Quaternion. + /// + [Test] + public void TestCastQuaternionItemToQuaternion() + { + TestHelpers.InMethod(); + + LSL_Types.Quaternion testValue = new LSL_Types.Quaternion(12.34, 56.44323, 765.983421, 0.00987); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, (LSL_Types.Quaternion)testList.Data[0]); + } + +//==================================================================================== + + /// + /// Tests GetLSLIntegerItem for LSLInteger item. + /// + [Test] + public void TestGetLSLIntegerItemForLSLIntegerItem() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testValue = new LSL_Types.LSLInteger(999911); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, testList.GetLSLIntegerItem(0)); + } + + /// + /// Tests GetLSLFloatItem for LSLFloat item. + /// + [Test] + public void TestGetLSLFloatItemForLSLFloatItem() + { + TestHelpers.InMethod(); + + LSL_Types.LSLFloat testValue = new LSL_Types.LSLFloat(321.45687876); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, testList.GetLSLFloatItem(0)); + } + + /// + /// Tests GetLSLFloatItem for LSLInteger item. + /// + [Test] + public void TestGetLSLFloatItemForLSLIntegerItem() + { + TestHelpers.InMethod(); + + LSL_Types.LSLInteger testValue = new LSL_Types.LSLInteger(3060987); + LSL_Types.LSLFloat testFloatValue = new LSL_Types.LSLFloat(testValue); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testFloatValue, testList.GetLSLFloatItem(0)); + } + + /// + /// Tests GetLSLStringItem for LSLString item. + /// + [Test] + public void TestGetLSLStringItemForLSLStringItem() + { + TestHelpers.InMethod(); + + LSL_Types.LSLString testValue = new LSL_Types.LSLString("hello all"); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, testList.GetLSLStringItem(0)); + } + + /// + /// Tests GetLSLStringItem for key item. + /// + [Test] + public void TestGetLSLStringItemForKeyItem() + { + TestHelpers.InMethod(); + + LSL_Types.key testValue + = new LSL_Types.key("98000000-0000-2222-3333-100000001000"); + LSL_Types.LSLString testStringValue = new LSL_Types.LSLString(testValue); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testStringValue, testList.GetLSLStringItem(0)); + } + + /// + /// Tests GetVector3Item for Vector3 item. + /// + [Test] + public void TestGetVector3ItemForVector3Item() + { + TestHelpers.InMethod(); + + LSL_Types.Vector3 testValue = new LSL_Types.Vector3(92.34, 58.98754, -0.10987); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, testList.GetVector3Item(0)); + } + /// + /// Tests GetQuaternionItem for Quaternion item. + /// + [Test] + public void TestGetQuaternionItemForQuaternionItem() + { + TestHelpers.InMethod(); + + LSL_Types.Quaternion testValue = new LSL_Types.Quaternion(12.64, 59.43723, 765.3421, 4.00987); + // make that nonsense a quaternion + testValue.Normalize(); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, testList.GetQuaternionItem(0)); + } + + /// + /// Tests GetKeyItem for key item. + /// + [Test] + public void TestGetKeyItemForKeyItem() + { + TestHelpers.InMethod(); + + LSL_Types.key testValue + = new LSL_Types.key("00000000-0000-2222-3333-100000001012"); + LSL_Types.list testList = new LSL_Types.list(testValue); + + Assert.AreEqual(testValue, testList.GetKeyItem(0)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestVector3.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestVector3.cs new file mode 100644 index 00000000000..0c838afa3c7 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/LSL_TypesTestVector3.cs @@ -0,0 +1,63 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using NUnit.Framework; +using OpenSim.Tests.Common; +using OpenSim.Region.ScriptEngine.Shared; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for Vector3 + /// + [TestFixture] + public class LSL_TypesTestVector3 : OpenSimTestCase + { + [Test] + public void TestDotProduct() + { + TestHelpers.InMethod(); + + // The numbers we test for. + Dictionary expectsSet = new Dictionary(); + expectsSet.Add("<1, 2, 3> * <2, 3, 4>", 20.0); + expectsSet.Add("<1, 2, 3> * <0, 0, 0>", 0.0); + + double result; + string[] parts; + string[] delim = { "*" }; + + foreach (KeyValuePair ex in expectsSet) + { + parts = ex.Key.Split(delim, System.StringSplitOptions.None); + result = new LSL_Types.Vector3(parts[0]) * new LSL_Types.Vector3(parts[1]); + Assert.AreEqual(ex.Value, result); + } + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAppearanceTest.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAppearanceTest.cs new file mode 100644 index 00000000000..c79132b5cec --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAppearanceTest.cs @@ -0,0 +1,166 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Text; + +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.AvatarFactory; +using OpenSim.Region.OptionalModules.World.NPC; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for OSSL_Api + /// + [TestFixture] + public class OSSL_ApiAppearanceTest : OpenSimTestCase + { + /* + protected Scene m_scene; + protected XEngine.XEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("NPC"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("OSSL"); + config.Set("DebuggerSafe", false); + config.Set("AllowOSFunctions", "true"); + config.Set("OSFunctionThreatLevel", "Severe"); + + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules(m_scene, initConfigSource, new AvatarFactoryModule(), new NPCModule()); + + m_engine = new XEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + [Test] + public void TestOsOwnerSaveAppearance() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID userId = TestHelpers.ParseTail(0x1); + float newHeight = 1.9f; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = newHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + string notecardName = "appearanceNc"; + + osslApi.osOwnerSaveAppearance(notecardName); + + IList items = part.Inventory.GetInventoryItems(notecardName); + Assert.That(items.Count, Is.EqualTo(1)); + + TaskInventoryItem ncItem = items[0]; + Assert.That(ncItem.Name, Is.EqualTo(notecardName)); + + AssetBase ncAsset = m_scene.AssetService.Get(ncItem.AssetID.ToString()); + Assert.That(ncAsset, Is.Not.Null); + + AssetNotecard anc = new AssetNotecard(UUID.Zero, ncAsset.Data); + anc.Decode(); + OSDMap appearanceOsd = (OSDMap)OSDParser.DeserializeLLSDXml(anc.BodyText); + AvatarAppearance savedAppearance = new AvatarAppearance(); + savedAppearance.Unpack(appearanceOsd); + + Assert.That(savedAppearance.AvatarHeight, Is.EqualTo(sp.Appearance.AvatarHeight)); + } + + [Test] + public void TestOsAgentSaveAppearance() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + UUID ownerId = TestHelpers.ParseTail(0x1); + UUID nonOwnerId = TestHelpers.ParseTail(0x2); + float newHeight = 1.9f; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, nonOwnerId); + sp.Appearance.AvatarHeight = newHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, ownerId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + string notecardName = "appearanceNc"; + + osslApi.osAgentSaveAppearance(new LSL_Types.LSLString(nonOwnerId.ToString()), notecardName); + + IList items = part.Inventory.GetInventoryItems(notecardName); + Assert.That(items.Count, Is.EqualTo(1)); + + TaskInventoryItem ncItem = items[0]; + Assert.That(ncItem.Name, Is.EqualTo(notecardName)); + + AssetBase ncAsset = m_scene.AssetService.Get(ncItem.AssetID.ToString()); + Assert.That(ncAsset, Is.Not.Null); + + AssetNotecard anc = new AssetNotecard(UUID.Zero, ncAsset.Data); + anc.Decode(); + OSDMap appearanceOsd = (OSDMap)OSDParser.DeserializeLLSDXml(anc.BodyText); + AvatarAppearance savedAppearance = new AvatarAppearance(); + savedAppearance.Unpack(appearanceOsd); + + Assert.That(savedAppearance.AvatarHeight, Is.EqualTo(sp.Appearance.AvatarHeight)); + } + */ + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAttachmentTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAttachmentTests.cs new file mode 100644 index 00000000000..a3ecc325489 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiAttachmentTests.cs @@ -0,0 +1,226 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.Attachments; +using OpenSim.Region.CoreModules.Framework.InventoryAccess; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for OSSL attachment functions + /// + /// + /// TODO: Add tests for all functions + /// + [TestFixture] + public class OSSL_ApiAttachmentTests : OpenSimTestCase + { + protected Scene m_scene; + protected XEngine.XEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + + IConfig xengineConfig = initConfigSource.AddConfig("XEngine"); + xengineConfig.Set("Enabled", "true"); + + IConfig oconfig = initConfigSource.AddConfig("OSSL"); + oconfig.Set("DebuggerSafe", false); + oconfig.Set("AllowOSFunctions", "true"); + oconfig.Set("OSFunctionThreatLevel", "Severe"); + + IConfig modulesConfig = initConfigSource.AddConfig("Modules"); + modulesConfig.Set("InventoryAccessModule", "BasicInventoryAccessModule"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules( + m_scene, initConfigSource, new AttachmentsModule(), new BasicInventoryAccessModule()); + + m_engine = new XEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + [Test] + public void TestOsForceAttachToAvatarFromInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string taskInvObjItemName = "sphere"; + UUID taskInvObjItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); + AttachmentPoint attachPoint = AttachmentPoint.Chin; + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(m_scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, ua1.PrincipalID); + SceneObjectGroup inWorldObj = SceneHelpers.AddSceneObject(m_scene, "inWorldObj", ua1.PrincipalID); + TaskInventoryItem scriptItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, inWorldObj.RootPart); + + new LSL_Api().Initialize(m_engine, inWorldObj.RootPart, scriptItem); + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, inWorldObj.RootPart, scriptItem); + +// SceneObjectGroup sog1 = SceneHelpers.CreateSceneObject(1, ua1.PrincipalID); + + // Create an object embedded inside the first + TaskInventoryHelpers.AddSceneObject(m_scene.AssetService, inWorldObj.RootPart, taskInvObjItemName, taskInvObjItemId, ua1.PrincipalID); + + osslApi.osForceAttachToAvatarFromInventory(taskInvObjItemName, (int)attachPoint); + + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.True); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments[0]; + Assert.That(attSo.Name, Is.EqualTo(taskInvObjItemName)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((uint)attachPoint)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check appearance status + List attachmentsInAppearance = sp.Appearance.GetAttachments(); + Assert.That(attachmentsInAppearance.Count, Is.EqualTo(1)); + Assert.That(sp.Appearance.GetAttachpoint(attachmentsInAppearance[0].ItemID), Is.EqualTo((uint)attachPoint)); + } + + /// + /// Make sure we can't force attach anything other than objects. + /// + [Test] + public void TestOsForceAttachToAvatarFromInventoryNotObject() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string taskInvObjItemName = "sphere"; + UUID taskInvObjItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); + AttachmentPoint attachPoint = AttachmentPoint.Chin; + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(m_scene, 0x1); + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, ua1.PrincipalID); + SceneObjectGroup inWorldObj = SceneHelpers.AddSceneObject(m_scene, "inWorldObj", ua1.PrincipalID); + TaskInventoryItem scriptItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, inWorldObj.RootPart); + + new LSL_Api().Initialize(m_engine, inWorldObj.RootPart, scriptItem); + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, inWorldObj.RootPart, scriptItem); + + // Create an object embedded inside the first + TaskInventoryHelpers.AddNotecard( + m_scene.AssetService, inWorldObj.RootPart, taskInvObjItemName, taskInvObjItemId, TestHelpers.ParseTail(0x900), "Hello World!"); + + bool exceptionCaught = false; + + try + { + osslApi.osForceAttachToAvatarFromInventory(taskInvObjItemName, (int)attachPoint); + } + catch (Exception) + { + exceptionCaught = true; + } + + Assert.That(exceptionCaught, Is.True); + + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.False); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(0)); + + // Check appearance status + List attachmentsInAppearance = sp.Appearance.GetAttachments(); + Assert.That(attachmentsInAppearance.Count, Is.EqualTo(0)); + } + + [Test] + public void TestOsForceAttachToOtherAvatarFromInventory() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string taskInvObjItemName = "sphere"; + UUID taskInvObjItemId = UUID.Parse("00000000-0000-0000-0000-100000000000"); + AttachmentPoint attachPoint = AttachmentPoint.Chin; + + UserAccount ua1 = UserAccountHelpers.CreateUserWithInventory(m_scene, "user", "one", 0x1, "pass"); + UserAccount ua2 = UserAccountHelpers.CreateUserWithInventory(m_scene, "user", "two", 0x2, "pass"); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, ua1); + SceneObjectGroup inWorldObj = SceneHelpers.AddSceneObject(m_scene, "inWorldObj", ua1.PrincipalID); + TaskInventoryItem scriptItem = TaskInventoryHelpers.AddScript(m_scene.AssetService, inWorldObj.RootPart); + + new LSL_Api().Initialize(m_engine, inWorldObj.RootPart, scriptItem); + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, inWorldObj.RootPart, scriptItem); + + // Create an object embedded inside the first + TaskInventoryHelpers.AddSceneObject( + m_scene.AssetService, inWorldObj.RootPart, taskInvObjItemName, taskInvObjItemId, ua1.PrincipalID); + + ScenePresence sp2 = SceneHelpers.AddScenePresence(m_scene, ua2); + + osslApi.osForceAttachToOtherAvatarFromInventory(sp2.UUID.ToString(), taskInvObjItemName, (int)attachPoint); + + // Check scene presence status + Assert.That(sp.HasAttachments(), Is.False); + List attachments = sp.GetAttachments(); + Assert.That(attachments.Count, Is.EqualTo(0)); + + Assert.That(sp2.HasAttachments(), Is.True); + List attachments2 = sp2.GetAttachments(); + Assert.That(attachments2.Count, Is.EqualTo(1)); + SceneObjectGroup attSo = attachments2[0]; + Assert.That(attSo.Name, Is.EqualTo(taskInvObjItemName)); + Assert.That(attSo.OwnerID, Is.EqualTo(ua2.PrincipalID)); + Assert.That(attSo.AttachmentPoint, Is.EqualTo((uint)attachPoint)); + Assert.That(attSo.IsAttachment); + Assert.That(attSo.UsesPhysics, Is.False); + Assert.That(attSo.IsTemporary, Is.False); + + // Check appearance status + List attachmentsInAppearance = sp.Appearance.GetAttachments(); + Assert.That(attachmentsInAppearance.Count, Is.EqualTo(0)); + + List attachmentsInAppearance2 = sp2.Appearance.GetAttachments(); + Assert.That(attachmentsInAppearance2.Count, Is.EqualTo(1)); + Assert.That(sp2.Appearance.GetAttachpoint(attachmentsInAppearance2[0].ItemID), Is.EqualTo((uint)attachPoint)); + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiNpcTests.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiNpcTests.cs new file mode 100644 index 00000000000..23aa7bfcc42 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/Shared/Tests/OSSL_ApiNpcTests.cs @@ -0,0 +1,343 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Region.CoreModules.Avatar.Attachments; +using OpenSim.Region.CoreModules.Avatar.AvatarFactory; +using OpenSim.Region.OptionalModules.World.NPC; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Shared.Api; +using OpenSim.Region.ScriptEngine.Shared.ScriptBase; +using OpenSim.Tests.Common; + +namespace OpenSim.Region.ScriptEngine.Shared.Tests +{ + /// + /// Tests for OSSL NPC API + /// + [TestFixture] + public class OSSL_NpcApiAppearanceTest : OpenSimTestCase + { + protected Scene m_scene; + protected XEngine.XEngine m_engine; + + [SetUp] + public override void SetUp() + { + base.SetUp(); + + IConfigSource initConfigSource = new IniConfigSource(); + IConfig config = initConfigSource.AddConfig("XEngine"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("NPC"); + config.Set("Enabled", "true"); + + config = initConfigSource.AddConfig("OSSL"); + config.Set("DebuggerSafe", false); + config.Set("AllowOSFunctions", "true"); + config.Set("OSFunctionThreatLevel", "Severe"); + + m_scene = new SceneHelpers().SetupScene(); + SceneHelpers.SetupSceneModules( + m_scene, initConfigSource, new AvatarFactoryModule(), new AttachmentsModule(), new NPCModule()); + + m_engine = new XEngine.XEngine(); + m_engine.Initialise(initConfigSource); + m_engine.AddRegion(m_scene); + } + + /// + /// Test creation of an NPC where the appearance data comes from a notecard + /// + [Test] + public void TestOsNpcCreateUsingAppearanceFromNotecard() + { + TestHelpers.InMethod(); + + // Store an avatar with a different height from default in a notecard. + UUID userId = TestHelpers.ParseTail(0x1); + float newHeight = 1.9f; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = newHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + string notecardName = "appearanceNc"; + osslApi.osOwnerSaveAppearance(notecardName); + + // Try creating a bot using the appearance in the notecard. + string npcRaw = osslApi.osNpcCreate("Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), notecardName); + Assert.That(npcRaw, Is.Not.Null); + + UUID npcId = new UUID(npcRaw); + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc, Is.Not.Null); + Assert.That(npc.Appearance.AvatarHeight, Is.EqualTo(newHeight)); + } + + [Test] + public void TestOsNpcCreateNotExistingNotecard() + { + TestHelpers.InMethod(); + + UUID userId = TestHelpers.ParseTail(0x1); + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, so.RootPart, null); + + bool gotExpectedException = false; + try + { + osslApi.osNpcCreate("Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), "not existing notecard name"); + } + catch (ScriptException) + { + gotExpectedException = true; + } + + Assert.That(gotExpectedException, Is.True); + } + + /// + /// Test creation of an NPC where the appearance data comes from an avatar already in the region. + /// + [Test] + public void TestOsNpcCreateUsingAppearanceFromAvatar() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Store an avatar with a different height from default in a notecard. + UUID userId = TestHelpers.ParseTail(0x1); + float newHeight = 1.9f; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = newHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + string notecardName = "appearanceNc"; + osslApi.osOwnerSaveAppearance(notecardName); + + // Try creating a bot using the existing avatar's appearance + string npcRaw = osslApi.osNpcCreate("Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), sp.UUID.ToString()); + Assert.That(npcRaw, Is.Not.Null); + + UUID npcId = new UUID(npcRaw); + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc, Is.Not.Null); + Assert.That(npc.Appearance.AvatarHeight, Is.EqualTo(newHeight)); + } + + [Test] + public void TestOsNpcLoadAppearance() + { + TestHelpers.InMethod(); + //TestHelpers.EnableLogging(); + + // Store an avatar with a different height from default in a notecard. + UUID userId = TestHelpers.ParseTail(0x1); + float firstHeight = 1.9f; + float secondHeight = 2.1f; + string firstAppearanceNcName = "appearanceNc1"; + string secondAppearanceNcName = "appearanceNc2"; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = firstHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + osslApi.osOwnerSaveAppearance(firstAppearanceNcName); + + string npcRaw + = osslApi.osNpcCreate("Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), firstAppearanceNcName); + + // Create a second appearance notecard with a different height + sp.Appearance.AvatarHeight = secondHeight; + osslApi.osOwnerSaveAppearance(secondAppearanceNcName); + + osslApi.osNpcLoadAppearance(npcRaw, secondAppearanceNcName); + + UUID npcId = new UUID(npcRaw); + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc, Is.Not.Null); + Assert.That(npc.Appearance.AvatarHeight, Is.EqualTo(secondHeight)); + } + + [Test] + public void TestOsNpcLoadAppearanceNotExistingNotecard() + { + TestHelpers.InMethod(); + + // Store an avatar with a different height from default in a notecard. + UUID userId = TestHelpers.ParseTail(0x1); + float firstHeight = 1.9f; +// float secondHeight = 2.1f; + string firstAppearanceNcName = "appearanceNc1"; + string secondAppearanceNcName = "appearanceNc2"; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = firstHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + osslApi.osOwnerSaveAppearance(firstAppearanceNcName); + + string npcRaw + = osslApi.osNpcCreate("Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), firstAppearanceNcName); + + bool gotExpectedException = false; + try + { + osslApi.osNpcLoadAppearance(npcRaw, secondAppearanceNcName); + } + catch (ScriptException) + { + gotExpectedException = true; + } + + Assert.That(gotExpectedException, Is.True); + + UUID npcId = new UUID(npcRaw); + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc, Is.Not.Null); + Assert.That(npc.Appearance.AvatarHeight, Is.EqualTo(firstHeight)); + } + + /// + /// Test removal of an owned NPC. + /// + [Test] + public void TestOsNpcRemoveOwned() + { + TestHelpers.InMethod(); + + // Store an avatar with a different height from default in a notecard. + UUID userId = TestHelpers.ParseTail(0x1); + UUID otherUserId = TestHelpers.ParseTail(0x2); + float newHeight = 1.9f; + + SceneHelpers.AddScenePresence(m_scene, otherUserId); + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = newHeight; + + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + SceneObjectGroup otherSo = SceneHelpers.CreateSceneObject(1, otherUserId, 0x20); + SceneObjectPart otherPart = otherSo.RootPart; + m_scene.AddSceneObject(otherSo); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + OSSL_Api otherOsslApi = new OSSL_Api(); + otherOsslApi.Initialize(m_engine, otherPart, null); + + string notecardName = "appearanceNc"; + osslApi.osOwnerSaveAppearance(notecardName); + + string npcRaw + = osslApi.osNpcCreate( + "Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), notecardName, ScriptBaseClass.OS_NPC_CREATOR_OWNED); + + otherOsslApi.osNpcRemove(npcRaw); + + // Should still be around + UUID npcId = new UUID(npcRaw); + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc, Is.Not.Null); + + osslApi.osNpcRemove(npcRaw); + + npc = m_scene.GetScenePresence(npcId); + + // Now the owner deleted it and it's gone + Assert.That(npc, Is.Null); + } + + /// + /// Test removal of an unowned NPC. + /// + [Test] + public void TestOsNpcRemoveUnowned() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + // Store an avatar with a different height from default in a notecard. + UUID userId = TestHelpers.ParseTail(0x1); + float newHeight = 1.9f; + + ScenePresence sp = SceneHelpers.AddScenePresence(m_scene, userId); + sp.Appearance.AvatarHeight = newHeight; + SceneObjectGroup so = SceneHelpers.CreateSceneObject(1, userId, 0x10); + SceneObjectPart part = so.RootPart; + m_scene.AddSceneObject(so); + + OSSL_Api osslApi = new OSSL_Api(); + osslApi.Initialize(m_engine, part, null); + + string notecardName = "appearanceNc"; + osslApi.osOwnerSaveAppearance(notecardName); + + string npcRaw + = osslApi.osNpcCreate( + "Jane", "Doe", new LSL_Types.Vector3(128, 128, 128), notecardName, ScriptBaseClass.OS_NPC_NOT_OWNED); + + osslApi.osNpcRemove(npcRaw); + + UUID npcId = new UUID(npcRaw); + ScenePresence npc = m_scene.GetScenePresence(npcId); + Assert.That(npc, Is.Null); + } + } +} diff --git a/Tests/OpenSim.Region.ScriptEngine.Tests/YEngine/XMREngXmrTestLs.cs b/Tests/OpenSim.Region.ScriptEngine.Tests/YEngine/XMREngXmrTestLs.cs new file mode 100644 index 00000000000..5fb3cb21574 --- /dev/null +++ b/Tests/OpenSim.Region.ScriptEngine.Tests/YEngine/XMREngXmrTestLs.cs @@ -0,0 +1,544 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Reflection; +using System.Text; + +using LSL_Float = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLFloat; +using LSL_Integer = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLInteger; +using LSL_Key = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_List = OpenSim.Region.ScriptEngine.Shared.LSL_Types.list; +using LSL_Rotation = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Quaternion; +using LSL_String = OpenSim.Region.ScriptEngine.Shared.LSL_Types.LSLString; +using LSL_Vector = OpenSim.Region.ScriptEngine.Shared.LSL_Types.Vector3; + +using SharedEventParams = OpenSim.Region.ScriptEngine.Shared.EventParams; +using SharedScriptBaseClass = OpenSim.Region.ScriptEngine.Shared.ScriptBase.ScriptBaseClass; + +namespace OpenSim.Region.ScriptEngine.Yengine +{ + public partial class Yengine + { + + private void XmrTestLs(string[] args, int indx) + { + bool flagFull = false; + bool flagQueues = false; + bool flagTopCPU = false; + int maxScripts = 0x7FFFFFFF; + int numScripts = 0; + string outName = null; + XMRInstance[] instances; + + // Decode command line options. + for(int i = indx; i < args.Length; i++) + { + if(args[i] == "-full") + { + flagFull = true; + continue; + } + if(args[i] == "-help") + { + m_logger?.LogInformation("[YEngine]: yeng ls -full -max= -out= -queues -topcpu"); + return; + } + if(args[i].StartsWith("-max=")) + { + try + { + maxScripts = Convert.ToInt32(args[i].Substring(5)); + } + catch(Exception e) + { + m_logger?.LogError("[YEngine]: bad max " + args[i].Substring(5) + ": " + e.Message); + return; + } + continue; + } + if(args[i].StartsWith("-out=")) + { + outName = args[i].Substring(5); + continue; + } + if(args[i] == "-queues") + { + flagQueues = true; + continue; + } + if(args[i] == "-topcpu") + { + flagTopCPU = true; + continue; + } + if(args[i][0] == '-') + { + m_logger?.LogError("[YEngine]: unknown option " + args[i] + ", try 'yeng ls -help'"); + return; + } + } + + TextWriter outFile = null; + if(outName != null) + { + try + { + outFile = File.CreateText(outName); + } + catch(Exception e) + { + m_logger?.LogError("[YEngine]: error creating " + outName + ": " + e.Message); + return; + } + } + else + { + outFile = new LogInfoTextWriter(m_log); + } + + try + { + // Scan instance list to find those that match selection criteria. + if(!Monitor.TryEnter(m_InstancesDict, 100)) + { + m_logger?.LogError("[YEngine]: deadlock m_LockedDict=" + m_LockedDict); + return; + } + try + { + instances = new XMRInstance[m_InstancesDict.Count]; + foreach(XMRInstance ins in m_InstancesDict.Values) + { + if(InstanceMatchesArgs(ins, args, indx)) + { + instances[numScripts++] = ins; + } + } + } + finally + { + Monitor.Exit(m_InstancesDict); + } + + // Maybe sort by descending CPU time. + if(flagTopCPU) + { + Array.Sort(instances, CompareInstancesByCPUTime); + } + + // Print the entries. + if(!flagFull) + { + outFile.WriteLine(" ItemID" + + " CPU(ms)" + + " NumEvents" + + " Status " + + " World Position " + + " :"); + } + for(int i = 0; (i < numScripts) && (i < maxScripts); i++) + { + outFile.WriteLine(instances[i].RunTestLs(flagFull)); + } + + // Print number of scripts that match selection criteria, + // even if we were told to print fewer. + outFile.WriteLine("total of {0} script(s)", numScripts); + + // If -queues given, print out queue contents too. + if(flagQueues) + { + LsQueue(outFile, "start", m_StartQueue, args, indx); + LsQueue(outFile, "sleep", m_SleepQueue, args, indx); + LsQueue(outFile, "yield", m_YieldQueue, args, indx); + } + } + finally + { + outFile.Close(); + } + } + + private void XmrTestPev(string[] args, int indx) + { + bool flagAll = false; + int numScripts = 0; + XMRInstance[] instances; + + // Decode command line options. + int i, j; + List selargs = new List(args.Length); + MethodInfo[] eventmethods = typeof(IEventHandlers).GetMethods(); + MethodInfo eventmethod; + for(i = indx; i < args.Length; i++) + { + string arg = args[i]; + if(arg == "-all") + { + flagAll = true; + continue; + } + if(arg == "-help") + { + m_logger?.LogInformation("[YEngine]: yeng pev -all | "); + return; + } + if(arg[0] == '-') + { + m_logger?.LogError("[YEngine]: unknown option " + arg + ", try 'yeng pev -help'"); + return; + } + for(j = 0; j < eventmethods.Length; j++) + { + eventmethod = eventmethods[j]; + if(eventmethod.Name == arg) + goto gotevent; + } + selargs.Add(arg); + } + m_logger?.LogError("[YEngine]: missing , try 'yeng pev -help'"); + return; + gotevent: + string eventname = eventmethod.Name; + StringBuilder sourcesb = new StringBuilder(); + while(++i < args.Length) + { + sourcesb.Append(' '); + sourcesb.Append(args[i]); + } + string sourcest = sourcesb.ToString(); + string sourcehash; + youveanerror = false; + Token t = TokenBegin.Construct("", null, ErrorMsg, sourcest, out sourcehash); + if(youveanerror) + return; + ParameterInfo[] paraminfos = eventmethod.GetParameters(); + object[] paramvalues = new object[paraminfos.Length]; + i = 0; + while(!((t = t.nextToken) is TokenEnd)) + { + if(i >= paramvalues.Length) + { + ErrorMsg(t, "extra parameter(s)"); + return; + } + paramvalues[i] = ParseParamValue(ref t); + if(paramvalues[i] == null) + return; + i++; + } + SharedEventParams eps = new SharedEventParams(eventname, paramvalues, zeroDetectParams); + + // Scan instance list to find those that match selection criteria. + if(!Monitor.TryEnter(m_InstancesDict, 100)) + { + m_logger?.LogError("[YEngine]: deadlock m_LockedDict=" + m_LockedDict); + return; + } + + try + { + instances = new XMRInstance[m_InstancesDict.Count]; + foreach(XMRInstance ins in m_InstancesDict.Values) + { + if(flagAll || InstanceMatchesArgs(ins, selargs.ToArray(), 0)) + { + instances[numScripts++] = ins; + } + } + } + finally + { + Monitor.Exit(m_InstancesDict); + } + + // Post event to the matching instances. + for(i = 0; i < numScripts; i++) + { + XMRInstance inst = instances[i]; + m_logger?.LogInformation("[YEngine]: post " + eventname + " to " + inst.m_DescName); + inst.PostEvent(eps); + } + } + + private object ParseParamValue(ref Token token) + { + if(token is TokenFloat) + { + return new LSL_Float(((TokenFloat)token).val); + } + if(token is TokenInt) + { + return new LSL_Integer(((TokenInt)token).val); + } + if(token is TokenStr) + { + return new LSL_String(((TokenStr)token).val); + } + if(token is TokenKwCmpLT) + { + List valuelist = new List(); + while(!((token = token.nextToken) is TokenKwCmpGT)) + { + if(!(token is TokenKwComma)) + { + object value = ParseParamValue(ref token); + if(value == null) + return null; + if(value is int) + value = (double)(int)value; + if(!(value is double)) + { + ErrorMsg(token, "must be float or integer constant"); + return null; + } + valuelist.Add((double)value); + } + else if(token.prevToken is TokenKwComma) + { + ErrorMsg(token, "missing constant"); + return null; + } + } + double[] values = valuelist.ToArray(); + switch(values.Length) + { + case 3: + { + return new LSL_Vector(values[0], values[1], values[2]); + } + case 4: + { + return new LSL_Rotation(values[0], values[1], values[2], values[3]); + } + default: + { + ErrorMsg(token, "not rotation or vector"); + return null; + } + } + } + if(token is TokenKwBrkOpen) + { + List valuelist = new List(); + while(!((token = token.nextToken) is TokenKwBrkClose)) + { + if(!(token is TokenKwComma)) + { + object value = ParseParamValue(ref token); + if(value == null) + return null; + valuelist.Add(value); + } + else if(token.prevToken is TokenKwComma) + { + ErrorMsg(token, "missing constant"); + return null; + } + } + return new LSL_List(valuelist.ToArray()); + } + if(token is TokenName) + { + FieldInfo field = typeof(SharedScriptBaseClass).GetField(((TokenName)token).val); + if((field != null) && field.IsPublic && (field.IsLiteral || (field.IsStatic && field.IsInitOnly))) + { + return field.GetValue(null); + } + } + ErrorMsg(token, "invalid constant"); + return null; + } + + private bool youveanerror; + private void ErrorMsg(Token token, string message) + { + youveanerror = true; + m_logger?.LogInformation("[YEngine]: " + token.posn + " " + message); + } + + private void XmrTestReset(string[] args, int indx) + { + bool flagAll = false; + int numScripts = 0; + XMRInstance[] instances; + + if(args.Length <= indx) + { + m_logger?.LogError("[YEngine]: must specify part of script name or -all for all scripts"); + return; + } + + // Decode command line options. + for(int i = indx; i < args.Length; i++) + { + if(args[i] == "-all") + { + flagAll = true; + continue; + } + if(args[i] == "-help") + { + m_logger?.LogInformation("[YEngine]: yeng reset -all | "); + return; + } + if(args[i][0] == '-') + { + m_logger?.LogError("[YEngine]: unknown option " + args[i] + ", try 'yeng reset -help'"); + return; + } + } + + // Scan instance list to find those that match selection criteria. + if(!Monitor.TryEnter(m_InstancesDict, 100)) + { + m_logger?.LogError("[YEngine]: deadlock m_LockedDict=" + m_LockedDict); + return; + } + + try + { + instances = new XMRInstance[m_InstancesDict.Count]; + foreach(XMRInstance ins in m_InstancesDict.Values) + { + if(flagAll || InstanceMatchesArgs(ins, args, indx)) + { + instances[numScripts++] = ins; + } + } + } + finally + { + Monitor.Exit(m_InstancesDict); + } + + // Reset the instances as if someone clicked their "Reset" button. + for(int i = 0; i < numScripts; i++) + { + XMRInstance inst = instances[i]; + m_logger?.LogInformation("[YEngine]: resetting " + inst.m_DescName); + inst.Reset(); + } + } + + private static int CompareInstancesByCPUTime(XMRInstance a, XMRInstance b) + { + if(a == null) + { + return (b == null) ? 0 : 1; + } + if(b == null) + { + return -1; + } + if(b.m_CPUTime < a.m_CPUTime) + return -1; + if(b.m_CPUTime > a.m_CPUTime) + return 1; + return 0; + } + + private void LsQueue(TextWriter outFile, string name, XMRInstQueue queue, string[] args, int indx) + { + outFile.WriteLine("Queue " + name + ":"); + lock(queue) + { + for(XMRInstance inst = queue.PeekHead(); inst != null; inst = inst.m_NextInst) + { + try + { + // Try to print instance name. + if(InstanceMatchesArgs(inst, args, indx)) + { + outFile.WriteLine(" " + inst.ItemID.ToString() + " " + inst.m_DescName); + } + } + catch(Exception e) + { + // Sometimes there are instances in the queue that are disposed. + outFile.WriteLine(" " + inst.ItemID.ToString() + " " + inst.m_DescName + ": " + e.Message); + } + } + } + } + + private bool InstanceMatchesArgs(XMRInstance ins, string[] args, int indx) + { + bool hadSomethingToCompare = false; + + for(int i = indx; i < args.Length; i++) + { + if(args[i][0] != '-') + { + hadSomethingToCompare = true; + if(ins.m_DescName.Contains(args[i])) + return true; + if(ins.ItemID.ToString().Contains(args[i])) + return true; + if(ins.AssetID.ToString().Contains(args[i])) + return true; + } + } + return !hadSomethingToCompare; + } + } + + /** + * @brief Make m_log.Info look like a text writer. + */ + public class LogInfoTextWriter: TextWriter + { + private StringBuilder sb = new StringBuilder(); + private ILogger m_logger; + public LogInfoTextWriter(ILogger m_log) + { + this.m_log = m_log; + } + public override void Write(char c) + { + if(c == '\n') + { + m_logger?.LogInformation("[YEngine]: " + sb.ToString()); + sb.Remove(0, sb.Length); + } + else + { + sb.Append(c); + } + } + public override void Close() + { + } + public override Encoding Encoding + { + get + { + return Encoding.UTF8; + } + } + } +} diff --git a/Tests/OpenSim.Robust.Tests/Clients/Grid/GridClient.cs b/Tests/OpenSim.Robust.Tests/Clients/Grid/GridClient.cs new file mode 100644 index 00000000000..15498517eb1 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Clients/Grid/GridClient.cs @@ -0,0 +1,133 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +using OpenMetaverse; +using NUnit.Framework; + +using OpenSim.Framework; +using OpenSim.Services.Interfaces; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; +using OpenSim.Services.Connectors; + +namespace Robust.Tests +{ + [TestFixture] + public class GridClient + { +// private static readonly ILog m_log = +// LogManager.GetLogger( +// MethodBase.GetCurrentMethod().DeclaringType); + + [Test] + public void Grid_001() + { + GridServicesConnector m_Connector = new GridServicesConnector(DemonServer.Address); + + GridRegion r1 = CreateRegion("Test Region 1", 1000, 1000); + GridRegion r2 = CreateRegion("Test Region 2", 1001, 1000); + GridRegion r3 = CreateRegion("Test Region 3", 1005, 1000); + + string msg = m_Connector.RegisterRegion(UUID.Zero, r1); + Assert.AreEqual(msg, string.Empty, "Region 1 failed to register"); + + msg = m_Connector.RegisterRegion(UUID.Zero, r2); + Assert.AreEqual(msg, string.Empty, "Region 2 failed to register"); + + msg = m_Connector.RegisterRegion(UUID.Zero, r3); + Assert.AreEqual(msg, string.Empty, "Region 3 failed to register"); + + bool success; + success = m_Connector.DeregisterRegion(r3.RegionID); + Assert.AreEqual(success, true, "Region 3 failed to deregister"); + + msg = m_Connector.RegisterRegion(UUID.Zero, r3); + Assert.AreEqual(msg, string.Empty, "Region 3 failed to re-register"); + + List regions = m_Connector.GetNeighbours(UUID.Zero, r1.RegionID); + Assert.AreNotEqual(regions, null, "GetNeighbours of region 1 failed"); + Assert.AreEqual(regions.Count, 1, "Region 1 should have 1 neighbor"); + Assert.AreEqual(regions[0].RegionName, "Test Region 2", "Region 1 has the wrong neighbor"); + + GridRegion region = m_Connector.GetRegionByUUID(UUID.Zero, r2.RegionID); + Assert.AreNotEqual(region, null, "GetRegionByUUID for region 2 failed"); + Assert.AreEqual(region.RegionName, "Test Region 2", "GetRegionByUUID of region 2 returned wrong region"); + + region = m_Connector.GetRegionByUUID(UUID.Zero, UUID.Random()); + Assert.AreEqual(region, null, "Region with randon id should not exist"); + + region = m_Connector.GetRegionByName(UUID.Zero, r3.RegionName); + Assert.AreNotEqual(region, null, "GetRegionByUUID for region 3 failed"); + Assert.AreEqual(region.RegionName, "Test Region 3", "GetRegionByUUID of region 3 returned wrong region"); + + region = m_Connector.GetRegionByName(UUID.Zero, "Foo"); + Assert.AreEqual(region, null, "Region Foo should not exist"); + + regions = m_Connector.GetRegionsByName(UUID.Zero, "Test", 10); + Assert.AreNotEqual(regions, null, "GetRegionsByName failed"); + Assert.AreEqual(regions.Count, 3, "GetRegionsByName should return 3"); + + regions = m_Connector.GetRegionRange(UUID.Zero, + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(1002), + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(1002) ); + Assert.AreNotEqual(regions, null, "GetRegionRange failed"); + Assert.AreEqual(regions.Count, 2, "GetRegionRange should return 2"); + + regions = m_Connector.GetRegionRange(UUID.Zero, + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(950), + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(950) ); + Assert.AreNotEqual(regions, null, "GetRegionRange (bis) failed"); + Assert.AreEqual(regions.Count, 0, "GetRegionRange (bis) should return 0"); + + // Deregister them all + success = m_Connector.DeregisterRegion(r1.RegionID); + Assert.AreEqual(success, true, "Region 1 failed to deregister"); + + success = m_Connector.DeregisterRegion(r2.RegionID); + Assert.AreEqual(success, true, "Region 2 failed to deregister"); + + success = m_Connector.DeregisterRegion(r3.RegionID); + Assert.AreEqual(success, true, "Region 3 failed to deregister"); + } + + private static GridRegion CreateRegion(string name, uint xcell, uint ycell) + { + GridRegion region = new GridRegion(xcell, ycell); + region.RegionName = name; + region.RegionID = UUID.Random(); + region.ExternalHostName = "127.0.0.1"; + region.HttpPort = 9000; + region.InternalEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("0.0.0.0"), 9000); + + return region; + } + } +} diff --git a/Tests/OpenSim.Robust.Tests/Clients/Grid/GridForm.html b/Tests/OpenSim.Robust.Tests/Clients/Grid/GridForm.html new file mode 100644 index 00000000000..252920f38d3 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Clients/Grid/GridForm.html @@ -0,0 +1,11 @@ + + +
+xmin: +xmax: +ymin: +ymax: + + +
+ diff --git a/Tests/OpenSim.Robust.Tests/Clients/InstantMessage/IMClient.cs b/Tests/OpenSim.Robust.Tests/Clients/InstantMessage/IMClient.cs new file mode 100644 index 00000000000..4eba7b90847 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Clients/InstantMessage/IMClient.cs @@ -0,0 +1,58 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +using OpenMetaverse; +using NUnit.Framework; + +using OpenSim.Framework; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors.InstantMessage; + +namespace Robust.Tests +{ + [TestFixture] + public class IMClient + { + [Test] + public void HGIM_001() + { + GridInstantMessage im = new GridInstantMessage(); + im.fromAgentID = new Guid(); + im.toAgentID = new Guid(); + im.message = "Hello"; + im.imSessionID = new Guid(); + + bool success = InstantMessageServiceConnector.SendInstantMessage(DemonServer.Address, im, String.Empty); + Assert.IsFalse(success, "Sending of IM succeeded, but it should have failed"); + } + + } +} diff --git a/Tests/OpenSim.Robust.Tests/Clients/Inventory/InventoryClient.cs b/Tests/OpenSim.Robust.Tests/Clients/Inventory/InventoryClient.cs new file mode 100644 index 00000000000..c416424b385 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Clients/Inventory/InventoryClient.cs @@ -0,0 +1,193 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Services.Connectors; + +using OpenSim.Tests.Common; + +namespace Robust.Tests +{ + [TestFixture] + public class InventoryClient + { + private UUID m_userID = new UUID("00000000-0000-0000-0000-333333333333"); + private UUID m_rootFolderID; + private UUID m_notecardsFolder; + private UUID m_objectsFolder; + + [Test] + public void Inventory_001_CreateInventory() + { + TestHelpers.InMethod(); + XInventoryServicesConnector m_Connector = new XInventoryServicesConnector(DemonServer.Address); + + // Create an inventory that looks like this: + // + // /My Inventory + // + // /Objects + // Some Object + // /Notecards + // Notecard 1 + // Notecard 2 + // /Test Folder + // Link to notecard -> /Notecards/Notecard 2 + // Link to Objects folder -> /Objects + + bool success = m_Connector.CreateUserInventory(m_userID); + Assert.IsTrue(success, "Failed to create user inventory"); + + m_rootFolderID = m_Connector.GetRootFolder(m_userID).ID; + Assert.That(UUID.Zero, Is.Not.EqualTo(m_rootFolderID), "Root folder ID must not be UUID.Zero"); + + InventoryFolderBase of = m_Connector.GetFolderForType(m_userID, FolderType.Object); + Assert.IsNotNull(of, "Failed to retrieve Objects folder"); + m_objectsFolder = of.ID; + Assert.That(UUID.Zero, Is.Not.EqualTo(m_objectsFolder), "Objects folder ID must not be UUID.Zero"); + + // Add an object + InventoryItemBase item = new InventoryItemBase(new UUID("b0000000-0000-0000-0000-00000000000b"), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Object; + item.Folder = m_objectsFolder; + item.Name = "Some Object"; + item.Description = string.Empty; + success = m_Connector.AddItem(item); + Assert.IsTrue(success, "Failed to add object to inventory"); + + InventoryFolderBase ncf = m_Connector.GetFolderForType(m_userID, FolderType.Notecard); + Assert.IsNotNull(of, "Failed to retrieve Notecards folder"); + m_notecardsFolder = ncf.ID; + Assert.That(UUID.Zero, Is.Not.EqualTo(m_notecardsFolder), "Notecards folder ID must not be UUID.Zero"); + m_notecardsFolder = ncf.ID; + + // Add a notecard + item = new InventoryItemBase(new UUID("10000000-0000-0000-0000-000000000001"), m_userID); + item.AssetID = UUID.Random(); + item.AssetType = (int)AssetType.Notecard; + item.Folder = m_notecardsFolder; + item.Name = "Test Notecard 1"; + item.Description = string.Empty; + success = m_Connector.AddItem(item); + Assert.IsTrue(success, "Failed to add Notecard 1 to inventory"); + // Add another notecard + item.ID = new UUID("20000000-0000-0000-0000-000000000002"); + item.AssetID = new UUID("a0000000-0000-0000-0000-00000000000a"); + item.Name = "Test Notecard 2"; + item.Description = string.Empty; + success = m_Connector.AddItem(item); + Assert.IsTrue(success, "Failed to add Notecard 2 to inventory"); + + // Add a folder + InventoryFolderBase folder = new InventoryFolderBase(new UUID("f0000000-0000-0000-0000-00000000000f"), "Test Folder", m_userID, m_rootFolderID); + folder.Type = (int)FolderType.None; + success = m_Connector.AddFolder(folder); + Assert.IsTrue(success, "Failed to add Test Folder to inventory"); + + // Add a link to notecard 2 in Test Folder + item.AssetID = item.ID; // use item ID of notecard 2 + item.ID = new UUID("40000000-0000-0000-0000-000000000004"); + item.AssetType = (int)AssetType.Link; + item.Folder = folder.ID; + item.Name = "Link to notecard"; + item.Description = string.Empty; + success = m_Connector.AddItem(item); + Assert.IsTrue(success, "Failed to add link to notecard to inventory"); + + // Add a link to the Objects folder in Test Folder + item.AssetID = m_Connector.GetFolderForType(m_userID, FolderType.Object).ID; // use item ID of Objects folder + item.ID = new UUID("50000000-0000-0000-0000-000000000005"); + item.AssetType = (int)AssetType.LinkFolder; + item.Folder = folder.ID; + item.Name = "Link to Objects folder"; + item.Description = string.Empty; + success = m_Connector.AddItem(item); + Assert.IsTrue(success, "Failed to add link to objects folder to inventory"); + + InventoryCollection coll = m_Connector.GetFolderContent(m_userID, m_rootFolderID); + Assert.IsNotNull(coll, "Failed to retrieve contents of root folder"); + Assert.Greater(coll.Folders.Count, 0, "Root folder does not have any subfolders"); + + coll = m_Connector.GetFolderContent(m_userID, folder.ID); + Assert.IsNotNull(coll, "Failed to retrieve contents of Test Folder"); + Assert.That(coll.Items.Count + coll.Folders.Count, Is.EqualTo(2), "Test Folder is expected to have exactly 2 things inside"); + + } + + [Test] + public void Inventory_002_MultipleItemsRequest() + { + TestHelpers.InMethod(); + XInventoryServicesConnector m_Connector = new XInventoryServicesConnector(DemonServer.Address); + + // Prefetch Notecard 1, will be cached from here on + InventoryItemBase item = m_Connector.GetItem(m_userID, new UUID("10000000-0000-0000-0000-000000000001")); + Assert.NotNull(item, "Failed to get Notecard 1"); + Assert.That(item.Name, Is.EqualTo("Test Notecard 1"), "Wrong name for Notecard 1"); + + UUID[] uuids = new UUID[2]; + uuids[0] = item.ID; + uuids[1] = new UUID("20000000-0000-0000-0000-000000000002"); + + InventoryItemBase[] items = m_Connector.GetMultipleItems(m_userID, uuids); + Assert.NotNull(items, "Failed to get multiple items"); + Assert.IsTrue(items.Length == 2, "Requested 2 items, but didn't receive 2 items"); + + // Now they should both be cached + items = m_Connector.GetMultipleItems(m_userID, uuids); + Assert.NotNull(items, "(Repeat) Failed to get multiple items"); + Assert.IsTrue(items.Length == 2, "(Repeat) Requested 2 items, but didn't receive 2 items"); + + // This item doesn't exist, but [0] does, and it's cached. + uuids[1] = new UUID("bb000000-0000-0000-0000-0000000000bb"); + // Fetching should return 2 items, but [1] should be null + items = m_Connector.GetMultipleItems(m_userID, uuids); + Assert.NotNull(items, "(Three times) Failed to get multiple items"); + Assert.IsTrue(items.Length == 2, "(Three times) Requested 2 items, but didn't receive 2 items"); + Assert.That(items[0].Name, Is.EqualTo("Test Notecard 1"), "(Three times) Wrong name for Notecard 1"); + Assert.IsNull(items[1], "(Three times) Expecting 2nd item to be null"); + + // Now both don't exist + uuids[0] = new UUID("aa000000-0000-0000-0000-0000000000aa"); + items = m_Connector.GetMultipleItems(m_userID, uuids); + Assert.Null(items[0], "Request to multiple non-existent items is supposed to return null [0]"); + Assert.Null(items[1], "Request to multiple non-existent items is supposed to return null [1]"); + + // This item exists, and it's not cached + uuids[1] = new UUID("b0000000-0000-0000-0000-00000000000b"); + // Fetching should return 2 items, but [0] should be null + items = m_Connector.GetMultipleItems(m_userID, uuids); + Assert.NotNull(items, "(Four times) Failed to get multiple items"); + Assert.IsTrue(items.Length == 2, "(Four times) Requested 2 items, but didn't receive 2 items"); + Assert.That(items[1].Name, Is.EqualTo("Some Object"), "(Four times) Wrong name for Some Object"); + Assert.IsNull(items[0], "(Four times) Expecting 1st item to be null"); + } + } +} diff --git a/Tests/OpenSim.Robust.Tests/Clients/Presence/PresenceClient.cs b/Tests/OpenSim.Robust.Tests/Clients/Presence/PresenceClient.cs new file mode 100644 index 00000000000..31c8ee96ef2 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Clients/Presence/PresenceClient.cs @@ -0,0 +1,81 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +using OpenMetaverse; +using NUnit.Framework; + +using OpenSim.Framework; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors; + +namespace Robust.Tests +{ + [TestFixture] + public class PresenceClient + { + [Test] + public void Presence_001() + { + PresenceServicesConnector m_Connector = new PresenceServicesConnector(DemonServer.Address); + + UUID user1 = UUID.Random(); + UUID session1 = UUID.Random(); + UUID region1 = UUID.Random(); + + bool success = m_Connector.LoginAgent(user1.ToString(), session1, UUID.Zero); + Assert.AreEqual(success, true, "Failed to add user session"); + + PresenceInfo pinfo = m_Connector.GetAgent(session1); + Assert.AreNotEqual(pinfo, null, "Unable to retrieve session"); + Assert.AreEqual(pinfo.UserID, user1.ToString(), "Retrieved session does not match expected userID"); + Assert.AreNotEqual(pinfo.RegionID, region1, "Retrieved session is unexpectedly in region"); + + success = m_Connector.ReportAgent(session1, region1); + Assert.AreEqual(success, true, "Failed to report session in region 1"); + + pinfo = m_Connector.GetAgent(session1); + Assert.AreNotEqual(pinfo, null, "Unable to session presence"); + Assert.AreEqual(pinfo.UserID, user1.ToString(), "Retrieved session does not match expected userID"); + Assert.AreEqual(pinfo.RegionID, region1, "Retrieved session is not in expected region"); + + success = m_Connector.LogoutAgent(session1); + Assert.AreEqual(success, true, "Failed to remove session"); + + pinfo = m_Connector.GetAgent(session1); + Assert.AreEqual(pinfo, null, "Session is still there, even though it shouldn't"); + + success = m_Connector.ReportAgent(session1, UUID.Random()); + Assert.AreEqual(success, false, "Remove non-existing session should fail"); + } + + } +} diff --git a/Tests/OpenSim.Robust.Tests/Clients/UserAccounts/UserAccountsClient.cs b/Tests/OpenSim.Robust.Tests/Clients/UserAccounts/UserAccountsClient.cs new file mode 100644 index 00000000000..3238dc9a31c --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Clients/UserAccounts/UserAccountsClient.cs @@ -0,0 +1,86 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; +using System.Reflection; + +using OpenMetaverse; +using NUnit.Framework; + +using OpenSim.Framework; +using OpenSim.Services.Interfaces; +using OpenSim.Services.Connectors; + +namespace Robust.Tests +{ + [TestFixture] + public class UserAccountsClient + { + [Test] + public void UserAccounts_001() + { + UserAccountServicesConnector m_Connector = new UserAccountServicesConnector(DemonServer.Address); + + string first = "Completely"; + string last = "Clueless"; + string email = "foo@bar.com"; + + UserAccount account = m_Connector.CreateUser(first, last, "123", email, UUID.Zero); + Assert.IsNotNull(account, "Failed to create account " + first + " " + last); + UUID user1 = account.PrincipalID; + + account = m_Connector.GetUserAccount(UUID.Zero, user1); + Assert.NotNull(account, "Failed to retrieve account for user id " + user1); + Assert.AreEqual(account.FirstName, first, "First name does not match"); + Assert.AreEqual(account.LastName, last, "Last name does not match"); + + account = m_Connector.GetUserAccount(UUID.Zero, first, last); + Assert.IsNotNull(account, "Failed to retrieve account for user " + first + " " + last); + Assert.AreEqual(account.FirstName, first, "First name does not match (bis)"); + Assert.AreEqual(account.LastName, last, "Last name does not match (bis)"); + + account.Email = "user@example.com"; + bool success = m_Connector.StoreUserAccount(account); + Assert.IsTrue(success, "Failed to store existing account"); + + account = m_Connector.GetUserAccount(UUID.Zero, user1); + Assert.NotNull(account, "Failed to retrieve account for user id " + user1); + Assert.AreEqual(account.Email, "user@example.com", "Incorrect email"); + + account = new UserAccount(UUID.Zero, "DoesNot", "Exist", "xxx@xxx.com"); + success = m_Connector.StoreUserAccount(account); + Assert.IsFalse(success, "Storing a non-existing account must fail"); + + account = m_Connector.GetUserAccount(UUID.Zero, "DoesNot", "Exist"); + Assert.IsNull(account, "Account DoesNot Exist must not be there"); + + } + + } +} diff --git a/Tests/OpenSim.Robust.Tests/Robust.Tests.csproj b/Tests/OpenSim.Robust.Tests/Robust.Tests.csproj new file mode 100644 index 00000000000..82e385bb100 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Robust.Tests.csproj @@ -0,0 +1,48 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Robust.Tests/Server/DemonServer.cs b/Tests/OpenSim.Robust.Tests/Server/DemonServer.cs new file mode 100644 index 00000000000..5bfa4b2cb36 --- /dev/null +++ b/Tests/OpenSim.Robust.Tests/Server/DemonServer.cs @@ -0,0 +1,66 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Threading; + + +using OpenSim.Server; + +namespace Robust.Tests +{ + [SetUpFixture] + public class DemonServer : OpenSimServer + { + private Thread m_demon; + + public static string Address = "http://localhost:8888"; + + [SetUp] + public void StartDemon() + { + if (File.Exists("Robust.Tests.log")) + File.Delete("Robust.Tests.log"); + + Console.WriteLine("**** Starting demon Robust server ****"); + m_demon = new Thread( () => Main(new string[] {"-inifile=Robust.Tests.ini"})); + m_demon.Start(); + // Give some time for the server to instantiate all services + Thread.Sleep(3000); + Console.WriteLine("**** Setup Finished ****"); + } + + [TearDown] + public void StopDemon() + { + Console.WriteLine("**** Killing demon Robust Server ****"); + m_Server.Shutdown(); + } + } +} diff --git a/Tests/OpenSim.Server.Handlers.Tests/Asset/Tests/AssetServerPostHandlerTests.cs b/Tests/OpenSim.Server.Handlers.Tests/Asset/Tests/AssetServerPostHandlerTests.cs new file mode 100644 index 00000000000..eb4b1181b6f --- /dev/null +++ b/Tests/OpenSim.Server.Handlers.Tests/Asset/Tests/AssetServerPostHandlerTests.cs @@ -0,0 +1,108 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Net; +using System.Text; +using System.Xml; +using System.Xml.Serialization; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Server.Handlers.Asset; +using OpenSim.Services.AssetService; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Server.Handlers.Asset.Test +{ + [TestFixture] + public class AssetServerPostHandlerTests : OpenSimTestCase + { + [Test] + public void TestGoodAssetStoreRequest() + { + TestHelpers.InMethod(); + + UUID assetId = TestHelpers.ParseTail(0x1); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + AssetService assetService = new AssetService(config); + + AssetServerPostHandler asph = new AssetServerPostHandler(assetService); + + AssetBase asset = AssetHelpers.CreateNotecardAsset(assetId, "Hello World"); + + MemoryStream buffer = new MemoryStream(); + + XmlWriterSettings settings = new XmlWriterSettings(); + settings.Encoding = Encoding.UTF8; + + using (XmlWriter writer = XmlWriter.Create(buffer, settings)) + { + XmlSerializer serializer = new XmlSerializer(typeof(AssetBase)); + serializer.Serialize(writer, asset); + writer.Flush(); + } + + buffer.Position = 0; + asph.Handle(null, buffer, null, null); + + AssetBase retrievedAsset = assetService.Get(assetId.ToString()); + + Assert.That(retrievedAsset, Is.Not.Null); + } + + [Test] + public void TestBadXmlAssetStoreRequest() + { + TestHelpers.InMethod(); + + IConfigSource config = new IniConfigSource(); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + AssetService assetService = new AssetService(config); + + AssetServerPostHandler asph = new AssetServerPostHandler(assetService); + + MemoryStream buffer = new MemoryStream(); + byte[] badData = new byte[] { 0x48, 0x65, 0x6c, 0x6c, 0x6f }; + buffer.Write(badData, 0, badData.Length); + buffer.Position = 0; + + TestOSHttpResponse response = new TestOSHttpResponse(); + asph.Handle(null, buffer, null, response); + + Assert.That(response.StatusCode, Is.EqualTo((int)HttpStatusCode.BadRequest)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Server.Handlers.Tests/OpenSim.Server.Handlers.Tests.csproj b/Tests/OpenSim.Server.Handlers.Tests/OpenSim.Server.Handlers.Tests.csproj new file mode 100644 index 00000000000..92a069a0bb2 --- /dev/null +++ b/Tests/OpenSim.Server.Handlers.Tests/OpenSim.Server.Handlers.Tests.csproj @@ -0,0 +1,57 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + ..\..\bin\XMLRPC.dll + False + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Services.InventoryService.Tests/OpenSim.Services.InventoryService.Tests.csproj b/Tests/OpenSim.Services.InventoryService.Tests/OpenSim.Services.InventoryService.Tests.csproj new file mode 100644 index 00000000000..d5fdf8a6972 --- /dev/null +++ b/Tests/OpenSim.Services.InventoryService.Tests/OpenSim.Services.InventoryService.Tests.csproj @@ -0,0 +1,56 @@ + + + net8.0 + enable + enable + + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Services.InventoryService.Tests/XInventoryServiceTests.cs b/Tests/OpenSim.Services.InventoryService.Tests/XInventoryServiceTests.cs new file mode 100644 index 00000000000..80b1e048e24 --- /dev/null +++ b/Tests/OpenSim.Services.InventoryService.Tests/XInventoryServiceTests.cs @@ -0,0 +1,174 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using NUnit.Framework; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Server.Base; +using OpenSim.Services.Interfaces; +using OpenSim.Tests.Common; + +namespace OpenSim.Services.InventoryService.Tests +{ + /// + /// Tests for the XInventoryService + /// + /// + /// TODO: Fill out more tests. + /// + [TestFixture] + public class XInventoryServiceTests : OpenSimTestCase + { + private IInventoryService CreateXInventoryService() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("InventoryService"); + config.Configs["InventoryService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + return ServerUtils.LoadPlugin( + "OpenSim.Services.InventoryService.dll:XInventoryService", new Object[] { config }); + } + + /// + /// Tests add item operation. + /// + /// + /// TODO: Test all operations. + /// + [Test] + public void TestAddItem() + { + TestHelpers.InMethod(); + + string creatorId = TestHelpers.ParseTail(0x1).ToString(); + UUID ownerId = TestHelpers.ParseTail(0x2); + UUID itemId = TestHelpers.ParseTail(0x10); + UUID assetId = TestHelpers.ParseTail(0x20); + UUID folderId = TestHelpers.ParseTail(0x30); + int invType = (int)InventoryType.Animation; + int assetType = (int)AssetType.Animation; + string itemName = "item1"; + + IInventoryService xis = CreateXInventoryService(); + + InventoryItemBase itemToStore + = new InventoryItemBase(itemId, ownerId) + { + CreatorIdentification = creatorId.ToString(), + AssetID = assetId, + Name = itemName, + Folder = folderId, + InvType = invType, + AssetType = assetType + }; + + Assert.That(xis.AddItem(itemToStore), Is.True); + + InventoryItemBase itemRetrieved = xis.GetItem(UUID.Zero, itemId); + + Assert.That(itemRetrieved, Is.Not.Null); + Assert.That(itemRetrieved.CreatorId, Is.EqualTo(creatorId)); + Assert.That(itemRetrieved.Owner, Is.EqualTo(ownerId)); + Assert.That(itemRetrieved.AssetID, Is.EqualTo(assetId)); + Assert.That(itemRetrieved.Folder, Is.EqualTo(folderId)); + Assert.That(itemRetrieved.InvType, Is.EqualTo(invType)); + Assert.That(itemRetrieved.AssetType, Is.EqualTo(assetType)); + Assert.That(itemRetrieved.Name, Is.EqualTo(itemName)); + } + + [Test] + public void TestUpdateItem() + { + TestHelpers.InMethod(); +// TestHelpers.EnableLogging(); + + string creatorId = TestHelpers.ParseTail(0x1).ToString(); + UUID ownerId = TestHelpers.ParseTail(0x2); + UUID itemId = TestHelpers.ParseTail(0x10); + UUID assetId = TestHelpers.ParseTail(0x20); + UUID folderId = TestHelpers.ParseTail(0x30); + int invType = (int)InventoryType.Animation; + int assetType = (int)AssetType.Animation; + string itemName = "item1"; + string itemName2 = "item2"; + + IInventoryService xis = CreateXInventoryService(); + + InventoryItemBase itemToStore + = new InventoryItemBase(itemId, ownerId) + { + CreatorIdentification = creatorId.ToString(), + AssetID = assetId, + Name = itemName, + Folder = folderId, + InvType = invType, + AssetType = assetType + }; + + Assert.That(xis.AddItem(itemToStore), Is.True); + + // Normal update + itemToStore.Name = itemName2; + + Assert.That(xis.UpdateItem(itemToStore), Is.True); + + InventoryItemBase itemRetrieved = xis.GetItem(UUID.Zero, itemId); + + Assert.That(itemRetrieved, Is.Not.Null); + Assert.That(itemRetrieved.Name, Is.EqualTo(itemName2)); + + // Attempt to update properties that should never change + string creatorId2 = TestHelpers.ParseTail(0x7).ToString(); + UUID ownerId2 = TestHelpers.ParseTail(0x8); + UUID folderId2 = TestHelpers.ParseTail(0x70); + int invType2 = (int)InventoryType.CallingCard; + int assetType2 = (int)AssetType.CallingCard; + string itemName3 = "item3"; + + itemToStore.CreatorIdentification = creatorId2.ToString(); + //itemToStore.Owner = ownerId2; this cant be done + itemToStore.Folder = folderId2; + itemToStore.InvType = invType2; + itemToStore.AssetType = assetType2; + itemToStore.Name = itemName3; + + Assert.That(xis.UpdateItem(itemToStore), Is.True); + + itemRetrieved = xis.GetItem(itemRetrieved.Owner, itemRetrieved.ID); + + Assert.That(itemRetrieved, Is.Not.Null); + Assert.That(itemRetrieved.CreatorId, Is.EqualTo(creatorId)); + Assert.That(itemRetrieved.Owner, Is.EqualTo(ownerId)); + Assert.That(itemRetrieved.AssetID, Is.EqualTo(assetId)); + Assert.That(itemRetrieved.Folder, Is.EqualTo(folderId)); + Assert.That(itemRetrieved.InvType, Is.EqualTo(invType)); + Assert.That(itemRetrieved.AssetType, Is.EqualTo(assetType)); + Assert.That(itemRetrieved.Name, Is.EqualTo(itemName3)); + } + } +} diff --git a/Tests/OpenSim.Stress.Tests/OpenSim.Tests.Stress.csproj b/Tests/OpenSim.Stress.Tests/OpenSim.Tests.Stress.csproj new file mode 100644 index 00000000000..0b4060fd695 --- /dev/null +++ b/Tests/OpenSim.Stress.Tests/OpenSim.Tests.Stress.csproj @@ -0,0 +1,58 @@ + + + net8.0 + enable + enable + false + true + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + ..\..\bin\XMLRPC.dll + False + + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + \ No newline at end of file diff --git a/Tests/OpenSim.Stress.Tests/VectorRenderModuleStressTests.cs b/Tests/OpenSim.Stress.Tests/VectorRenderModuleStressTests.cs new file mode 100644 index 00000000000..9ace9a995f0 --- /dev/null +++ b/Tests/OpenSim.Stress.Tests/VectorRenderModuleStressTests.cs @@ -0,0 +1,121 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Scripting.DynamicTexture; +using OpenSim.Region.CoreModules.Scripting.VectorRender; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Tests.Common; + +namespace OpenSim.Tests.Stress +{ + [TestFixture] + public class VectorRenderModuleStressTests : OpenSimTestCase + { + public Scene Scene { get; private set; } + public DynamicTextureModule Dtm { get; private set; } + public VectorRenderModule Vrm { get; private set; } + + private void SetupScene(bool reuseTextures) + { + Scene = new SceneHelpers().SetupScene(); + + Dtm = new DynamicTextureModule(); + Dtm.ReuseTextures = reuseTextures; + + Vrm = new VectorRenderModule(); + + SceneHelpers.SetupSceneModules(Scene, Dtm, Vrm); + } + + [Test] + public void TestConcurrentRepeatedDraw() + { + int threads = 4; + TestHelpers.InMethod(); + + SetupScene(false); + + List drawers = new List(); + + for (int i = 0; i < threads; i++) + { + Drawer d = new Drawer(this, i); + drawers.Add(d); + Console.WriteLine("Starting drawer {0}", i); + Util.FireAndForget(o => d.Draw(), null, "VectorRenderModuleStressTests.TestConcurrentRepeatedDraw"); + } + + Thread.Sleep(10 * 60 * 1000); + + drawers.ForEach(d => d.Ready = false); + drawers.ForEach(d => Console.WriteLine("Drawer {0} drew {1} textures", d.Number, d.Pass + 1)); + } + + class Drawer + { + public int Number { get; private set; } + public int Pass { get; private set; } + public bool Ready { get; set; } + + private VectorRenderModuleStressTests m_tests; + + public Drawer(VectorRenderModuleStressTests tests, int number) + { + m_tests = tests; + Number = number; + Ready = true; + } + + public void Draw() + { + SceneObjectGroup so = SceneHelpers.AddSceneObject(m_tests.Scene); + + while (Ready) + { + UUID originalTextureID = so.RootPart.Shape.Textures.GetFace(0).TextureID; + + // Ensure unique text + string text = string.Format("{0:D2}{1}", Number, Pass); + + m_tests.Dtm.AddDynamicTextureData( + m_tests.Scene.RegionInfo.RegionID, + so.UUID, + m_tests.Vrm.GetContentType(), + string.Format("PenColour BLACK; MoveTo 40,220; FontSize 32; Text {0};", text), + ""); + + Assert.That(originalTextureID, Is.Not.EqualTo(so.RootPart.Shape.Textures.GetFace(0).TextureID)); + + Pass++; + } + } + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/GlobalUsings.cs b/Tests/OpenSim.Tests.Common/GlobalUsings.cs new file mode 100644 index 00000000000..7fef4b0e1e0 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/GlobalUsings.cs @@ -0,0 +1,2 @@ +global using Xunit; +global using FluentAssertions; \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Helpers/AssetHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/AssetHelpers.cs new file mode 100644 index 00000000000..974da4ca2bd --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/AssetHelpers.cs @@ -0,0 +1,168 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Text; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.Framework.Scenes.Serialization; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Tests.Common +{ + public class AssetHelpers + { + /// + /// Create a notecard asset with a random uuids and dummy text. + /// + /// + public static AssetBase CreateNotecardAsset() + { + return CreateNotecardAsset(UUID.Random()); + } + + /// + /// Create a notecard asset with dummy text and a random owner. + /// + /// /param> + /// + public static AssetBase CreateNotecardAsset(UUID assetId) + { + return CreateNotecardAsset(assetId, "hello"); + } + + /// + /// Create a notecard asset with a random owner. + /// + /// /param> + /// + /// + public static AssetBase CreateNotecardAsset(UUID assetId, string text) + { + return CreateAsset(assetId, AssetType.Notecard, text, UUID.Random()); + } + +// /// +// /// Create and store a notecard asset with a random uuid and dummy text. +// /// +// /// /param> +// /// +// public static AssetBase CreateNotecardAsset(Scene scene, UUID creatorId) +// { +// AssetBase asset = CreateAsset(UUID.Random(), AssetType.Notecard, "hello", creatorId); +// scene.AssetService.Store(asset); +// return asset; +// } + + /// + /// Create an asset from the given object. + /// + /// + /// The hexadecimal last part of the UUID for the asset created. A UUID of the form "00000000-0000-0000-0000-{0:XD12}" + /// will be used. + /// + /// + /// + public static AssetBase CreateAsset(int assetUuidTail, SceneObjectGroup sog) + { + return CreateAsset(new UUID(string.Format("00000000-0000-0000-0000-{0:X12}", assetUuidTail)), sog); + } + + /// + /// Create an asset from the given object. + /// + /// + /// + /// + public static AssetBase CreateAsset(UUID assetUuid, SceneObjectGroup sog) + { + return CreateAsset( + assetUuid, + AssetType.Object, + Encoding.ASCII.GetBytes(SceneObjectSerializer.ToOriginalXmlFormat(sog)), + sog.OwnerID); + } + + /// + /// Create an asset from the given scene object. + /// + /// + /// The hexadecimal last part of the UUID for the asset created. A UUID of the form "00000000-0000-0000-0000-{0:XD12}" + /// will be used. + /// + /// + /// + public static AssetBase CreateAsset(int assetUuidTail, CoalescedSceneObjects coa) + { + return CreateAsset(new UUID(string.Format("00000000-0000-0000-0000-{0:X12}", assetUuidTail)), coa); + } + + /// + /// Create an asset from the given scene object. + /// + /// + /// + /// + public static AssetBase CreateAsset(UUID assetUuid, CoalescedSceneObjects coa) + { + return CreateAsset( + assetUuid, + AssetType.Object, + Encoding.ASCII.GetBytes(CoalescedSceneObjectsSerializer.ToXml(coa)), + coa.CreatorId); + } + + /// + /// Create an asset from the given data. + /// + public static AssetBase CreateAsset(UUID assetUuid, AssetType assetType, string text, UUID creatorID) + { + AssetNotecard anc = new AssetNotecard(); + anc.BodyText = text; + anc.Encode(); + + return CreateAsset(assetUuid, assetType, anc.AssetData, creatorID); + } + + /// + /// Create an asset from the given data. + /// + public static AssetBase CreateAsset(UUID assetUuid, AssetType assetType, byte[] data, UUID creatorID) + { + AssetBase asset = new AssetBase(assetUuid, assetUuid.ToString(), (sbyte)assetType, creatorID.ToString()); + asset.Data = data; + return asset; + } + + public static string ReadAssetAsString(IAssetService assetService, UUID uuid) + { + byte[] assetData = assetService.GetData(uuid.ToString()); + return Encoding.ASCII.GetString(assetData); + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Helpers/BaseRequestHandlerHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/BaseRequestHandlerHelpers.cs new file mode 100644 index 00000000000..48310728f90 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/BaseRequestHandlerHelpers.cs @@ -0,0 +1,85 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.Text; + +using OpenSim.Framework; +using OpenSim.Framework.Servers; +using OpenSim.Framework.Servers.HttpServer; + +namespace OpenSim.Tests.Common +{ + public class BaseRequestHandlerHelpers + { + private static string[] m_emptyStringArray = new string[] { }; + + public static void BaseTestGetParams(BaseRequestHandler handler, string assetsPath) + { + handler.GetParam(null).Should().BeEmpty("Failed on null path."); + handler.GetParam("").Should().BeEmpty("Failed on empty path"); + handler.GetParam("s").Should().BeEmpty("Failed on short URL"); + handler.GetParam("corruptUrl").Should().BeEmpty("Failed on Corrupt URL"); + //Assert.AreEqual(String.Empty, handler.GetParam(null), "Failed on null path."); + //Assert.AreEqual(String.Empty, handler.GetParam(""), "Failed on empty path."); + //Assert.AreEqual(String.Empty, handler.GetParam("s"), "Failed on short url."); + //Assert.AreEqual(String.Empty, handler.GetParam("corruptUrl"), "Failed on corruptUrl."); + + handler.GetParam(assetsPath).Should().BeEmpty(""); + handler.GetParam(assetsPath + "/").Should().Be("/"); + handler.GetParam(assetsPath + "/a").Should().Be("/a"); + handler.GetParam(assetsPath + "/b/").Should().Be("/b/"); + handler.GetParam(assetsPath + "/c/d").Should().Be("/c/d"); + handler.GetParam(assetsPath + "/e/f/").Should().Be("/e/f/"); + //Assert.AreEqual(String.Empty, handler.GetParam(assetsPath)); + //Assert.AreEqual("/", handler.GetParam(assetsPath + "/")); + //Assert.AreEqual("/a", handler.GetParam(assetsPath + "/a")); + //Assert.AreEqual("/b/", handler.GetParam(assetsPath + "/b/")); + //Assert.AreEqual("/c/d", handler.GetParam(assetsPath + "/c/d")); + //Assert.AreEqual("/e/f/", handler.GetParam(assetsPath + "/e/f/")); + } + + public static void BaseTestSplitParams(BaseRequestHandler handler, string assetsPath) + { + // Assert.AreEqual(m_emptyStringArray, handler.SplitParams(null), "Failed on null."); + // Assert.AreEqual(m_emptyStringArray, handler.SplitParams(""), "Failed on empty path."); + // Assert.AreEqual(m_emptyStringArray, handler.SplitParams("corruptUrl"), "Failed on corrupt url."); + + // Assert.AreEqual(m_emptyStringArray, handler.SplitParams(assetsPath), "Failed on empty params."); + // Assert.AreEqual(m_emptyStringArray, handler.SplitParams(assetsPath + "/"), "Failed on single slash."); + + // Assert.AreEqual(new string[] { "a" }, handler.SplitParams(assetsPath + "/a"), "Failed on first segment."); + // Assert.AreEqual(new string[] { "b" }, handler.SplitParams(assetsPath + "/b/"), "Failed on second slash."); + // Assert.AreEqual(new string[] { "c", "d" }, handler.SplitParams(assetsPath + "/c/d"), "Failed on second segment."); + // Assert.AreEqual(new string[] { "e", "f" }, handler.SplitParams(assetsPath + "/e/f/"), "Failed on trailing slash."); + } + + public static byte[] EmptyByteArray = new byte[] {}; + + } +} diff --git a/Tests/OpenSim.Tests.Common/Helpers/ClientStackHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/ClientStackHelpers.cs new file mode 100644 index 00000000000..cfb776b2467 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/ClientStackHelpers.cs @@ -0,0 +1,95 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.Packets; +using OpenSim.Framework; +using OpenSim.Region.ClientStack.LindenUDP; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Tests.Common +{ + /// + /// This class adds full UDP client classes and associated scene presence to scene. + /// + /// + /// This is used for testing client stack code. For testing other code, use SceneHelper methods instead since + /// they operate without the burden of setting up UDP structures which should be unnecessary for testing scene + /// code. + /// + public static class ClientStackHelpers + { + public static ScenePresence AddChildClient( + Scene scene, LLUDPServer udpServer, UUID agentId, UUID sessionId, uint circuitCode) + { + IPEndPoint testEp = new IPEndPoint(IPAddress.Loopback, 999); + + UseCircuitCodePacket uccp = new UseCircuitCodePacket(); + + UseCircuitCodePacket.CircuitCodeBlock uccpCcBlock + = new UseCircuitCodePacket.CircuitCodeBlock(); + uccpCcBlock.Code = circuitCode; + uccpCcBlock.ID = agentId; + uccpCcBlock.SessionID = sessionId; + uccp.CircuitCode = uccpCcBlock; + + byte[] uccpBytes = uccp.ToBytes(); + UDPPacketBuffer upb = new UDPPacketBuffer(testEp, uccpBytes.Length); + upb.DataLength = uccpBytes.Length; // God knows why this isn't set by the constructor. + Buffer.BlockCopy(uccpBytes, 0, upb.Data, 0, uccpBytes.Length); + + AgentCircuitData acd = new AgentCircuitData(); + acd.AgentID = agentId; + acd.SessionID = sessionId; + + scene.AuthenticateHandler.AddNewCircuit(circuitCode, acd); + + udpServer.PacketReceived(upb); + + return scene.GetScenePresence(agentId); + } + + public static TestLLUDPServer AddUdpServer(Scene scene) + { + return AddUdpServer(scene, new IniConfigSource()); + } + + public static TestLLUDPServer AddUdpServer(Scene scene, IniConfigSource configSource) + { + uint port = 0; + AgentCircuitManager acm = scene.AuthenticateHandler; + + TestLLUDPServer udpServer = new TestLLUDPServer(IPAddress.Any, port, 0, configSource, acm); + udpServer.AddScene(scene); + + return udpServer; + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Helpers/EntityTransferHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/EntityTransferHelpers.cs new file mode 100644 index 00000000000..70a375aedf3 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/EntityTransferHelpers.cs @@ -0,0 +1,97 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Tests.Common +{ + public static class EntityTransferHelpers + { + /// + /// Set up correct handling of the InformClientOfNeighbour call from the source region that triggers the + /// viewer to setup a connection with the destination region. + /// + /// + /// + /// A list that will be populated with any TestClients set up in response to + /// being informed about a destination region. + /// + public static void SetupInformClientOfNeighbourTriggersNeighbourClientCreate( + TestClient tc, List neighbourTcs) + { + // XXX: Confusingly, this is also used for non-neighbour notification (as in teleports that do not use the + // event queue). + + tc.OnTestClientInformClientOfNeighbour += (neighbourHandle, neighbourExternalEndPoint) => + { + uint x, y; + Util.RegionHandleToRegionLoc(neighbourHandle, out x, out y); + + AgentCircuitData newAgent = tc.RequestClientInfo(); + + Scene neighbourScene; + SceneManager.Instance.TryGetScene(x, y, out neighbourScene); + + TestClient neighbourTc = new TestClient(newAgent, neighbourScene); + neighbourTcs.Add(neighbourTc); + neighbourScene.AddNewAgent(neighbourTc, PresenceType.User); + }; + } + + /// + /// Set up correct handling of the InformClientOfNeighbour call from the source region that triggers the + /// viewer to setup a connection with the destination region. + /// + /// + /// + /// A list that will be populated with any TestClients set up in response to + /// being informed about a destination region. + /// + public static void SetupSendRegionTeleportTriggersDestinationClientCreateAndCompleteMovement( + TestClient client, List destinationClients) + { + client.OnTestClientSendRegionTeleport + += (regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL) => + { + uint x, y; + Util.RegionHandleToRegionLoc(regionHandle, out x, out y); + + AgentCircuitData newAgent = client.RequestClientInfo(); + + Scene destinationScene; + SceneManager.Instance.TryGetScene(x, y, out destinationScene); + + TestClient destinationClient = new TestClient(newAgent, destinationScene); + destinationClients.Add(destinationClient); + destinationScene.AddNewAgent(destinationClient, PresenceType.User); + + ThreadPool.UnsafeQueueUserWorkItem(o => destinationClient.CompleteMovement(), null); + }; + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Helpers/SceneHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/SceneHelpers.cs new file mode 100644 index 00000000000..9e3cec6adfa --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/SceneHelpers.cs @@ -0,0 +1,738 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Net; + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Framework.Console; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.Avatar.Gods; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Asset; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Authentication; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Inventory; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Grid; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.UserAccounts; +using OpenSim.Region.CoreModules.ServiceConnectorsOut.Presence; +using OpenSim.Region.PhysicsModule.BasicPhysics; +using OpenSim.Region.PhysicsModules.SharedBase; +using OpenSim.Services.Interfaces; + +using BaseServerUtils = OpenSim.Server.Base.ServerUtils; +using Nini.Config; +using OpenSim.Data.Null; + + +namespace OpenSim.Tests.Common +{ + /// + /// Helpers for setting up scenes. + /// + public class SceneHelpers + { + /// + /// We need a scene manager so that test clients can retrieve a scene when performing teleport tests. + /// + public SceneManager SceneManager { get; private set; } + + public ISimulationDataService SimDataService { get; private set; } + + private AgentCircuitManager m_acm = new AgentCircuitManager(); + private IEstateDataService m_estateDataService = null; + + private LocalAssetServicesConnector m_assetService; + private LocalAuthenticationServicesConnector m_authenticationService; + private LocalInventoryServicesConnector m_inventoryService; + private RegionGridServicesConnector m_gridService; + private LocalUserAccountServicesConnector m_userAccountService; + private LocalPresenceServicesConnector m_presenceService; + + private TestsAssetCache m_cache; + + private PhysicsScene m_physicsScene; + + public SceneHelpers() : this(null) {} + + public SceneHelpers(TestsAssetCache cache) + { + SceneManager = new SceneManager(); + + m_assetService = StartAssetService(cache); + m_authenticationService = StartAuthenticationService(); + m_inventoryService = StartInventoryService(); + m_gridService = StartGridService(); + m_userAccountService = StartUserAccountService(); + m_presenceService = StartPresenceService(); + + m_inventoryService.PostInitialise(); + m_assetService.PostInitialise(); + m_userAccountService.PostInitialise(); + m_presenceService.PostInitialise(); + + m_cache = cache; + + m_physicsScene = StartPhysicsScene(); + + SimDataService = BaseServerUtils.LoadPlugin("OpenSim.Tests.Common.dll", null); + } + + /// + /// Set up a test scene + /// + /// + /// Automatically starts services, as would the normal runtime. + /// + /// + public TestScene SetupScene() + { + return SetupScene("Unit test region", UUID.Random(), 1000, 1000); + } + + public TestScene SetupScene(string name, UUID id, uint x, uint y) + { + return SetupScene(name, id, x, y, new IniConfigSource()); + } + + public TestScene SetupScene(string name, UUID id, uint x, uint y, IConfigSource configSource) + { + return SetupScene(name, id, x, y, Constants.RegionSize, Constants.RegionSize, configSource); + } + + /// + /// Set up a scene. + /// + /// Name of the region + /// ID of the region + /// X co-ordinate of the region + /// Y co-ordinate of the region + /// X size of scene + /// Y size of scene + /// + /// + public TestScene SetupScene( + string name, UUID id, uint x, uint y, uint sizeX, uint sizeY, IConfigSource configSource) + { + Console.WriteLine("Setting up test scene {0}", name); + + // We must set up a console otherwise setup of some modules may fail + MainConsole.Instance = new MockConsole(); + + RegionInfo regInfo = new RegionInfo(x, y, new IPEndPoint(IPAddress.Loopback, 9000), "127.0.0.1"); + regInfo.RegionName = name; + regInfo.RegionID = id; + regInfo.RegionSizeX = sizeX; + regInfo.RegionSizeY = sizeY; + regInfo.ServerURI = "http://127.0.0.1:9000/"; + + + TestScene testScene = new TestScene( + regInfo, m_acm, SimDataService, m_estateDataService, configSource, null); + + testScene.RegionInfo.EstateSettings = new EstateSettings(); + testScene.RegionInfo.EstateSettings.EstateOwner = UUID.Random(); + + INonSharedRegionModule godsModule = new GodsModule(); + godsModule.Initialise(new IniConfigSource()); + godsModule.AddRegion(testScene); + + // Add scene to physics + ((INonSharedRegionModule)m_physicsScene).AddRegion(testScene); + ((INonSharedRegionModule)m_physicsScene).RegionLoaded(testScene); + + // Add scene to services + m_assetService.AddRegion(testScene); + + if (m_cache != null) + { + m_cache.AddRegion(testScene); + m_cache.RegionLoaded(testScene); + testScene.AddRegionModule(m_cache.Name, m_cache); + } + + m_assetService.RegionLoaded(testScene); + testScene.AddRegionModule(m_assetService.Name, m_assetService); + + m_authenticationService.AddRegion(testScene); + m_authenticationService.RegionLoaded(testScene); + testScene.AddRegionModule(m_authenticationService.Name, m_authenticationService); + + m_inventoryService.AddRegion(testScene); + m_inventoryService.RegionLoaded(testScene); + testScene.AddRegionModule(m_inventoryService.Name, m_inventoryService); + + m_gridService.AddRegion(testScene); + m_gridService.RegionLoaded(testScene); + testScene.AddRegionModule(m_gridService.Name, m_gridService); + + m_userAccountService.AddRegion(testScene); + m_userAccountService.RegionLoaded(testScene); + testScene.AddRegionModule(m_userAccountService.Name, m_userAccountService); + + m_presenceService.AddRegion(testScene); + m_presenceService.RegionLoaded(testScene); + testScene.AddRegionModule(m_presenceService.Name, m_presenceService); + + testScene.SetModuleInterfaces(); + + testScene.LandChannel = new TestLandChannel(testScene); + testScene.LoadWorldMap(); + + testScene.LoginsEnabled = true; + testScene.RegisterRegionWithGrid(); + + SceneManager.Add(testScene); + + return testScene; + } + + private static LocalAssetServicesConnector StartAssetService(TestsAssetCache cache) + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.Configs["Modules"].Set("AssetServices", "LocalAssetServicesConnector"); + config.AddConfig("AssetService"); + config.Configs["AssetService"].Set("LocalServiceModule", "OpenSim.Services.AssetService.dll:AssetService"); + config.Configs["AssetService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalAssetServicesConnector assetService = new LocalAssetServicesConnector(); + assetService.Initialise(config); + + if (cache != null) + { + IConfigSource cacheConfig = new IniConfigSource(); + cacheConfig.AddConfig("Modules"); + cacheConfig.Configs["Modules"].Set("AssetCaching", "TestsAssetCache"); + cacheConfig.AddConfig("AssetCache"); + + cache.Initialise(cacheConfig); + } + + return assetService; + } + + private static LocalAuthenticationServicesConnector StartAuthenticationService() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("AuthenticationService"); + config.Configs["Modules"].Set("AuthenticationServices", "LocalAuthenticationServicesConnector"); + config.Configs["AuthenticationService"].Set( + "LocalServiceModule", "OpenSim.Services.AuthenticationService.dll:PasswordAuthenticationService"); + config.Configs["AuthenticationService"].Set("StorageProvider", "OpenSim.Data.Null.dll"); + + LocalAuthenticationServicesConnector service = new LocalAuthenticationServicesConnector(); + service.Initialise(config); + + return service; + } + + private static LocalInventoryServicesConnector StartInventoryService() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("InventoryService"); + config.Configs["Modules"].Set("InventoryServices", "LocalInventoryServicesConnector"); + config.Configs["InventoryService"].Set("LocalServiceModule", "OpenSim.Services.InventoryService.dll:XInventoryService"); + config.Configs["InventoryService"].Set("StorageProvider", "OpenSim.Tests.Common.dll"); + + LocalInventoryServicesConnector inventoryService = new LocalInventoryServicesConnector(); + inventoryService.Initialise(config); + + return inventoryService; + } + + private static RegionGridServicesConnector StartGridService() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("GridService"); + config.Configs["Modules"].Set("GridServices", "RegionGridServicesConnector"); + config.Configs["GridService"].Set("StorageProvider", "OpenSim.Data.Null.dll:NullRegionData"); + config.Configs["GridService"].Set("LocalServiceModule", "OpenSim.Services.GridService.dll:GridService"); + config.Configs["GridService"].Set("ConnectionString", "!static"); + + RegionGridServicesConnector gridService = new RegionGridServicesConnector(); + gridService.Initialise(config); + + return gridService; + } + + /// + /// Start a user account service + /// + /// + /// + private static LocalUserAccountServicesConnector StartUserAccountService() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("UserAccountService"); + config.Configs["Modules"].Set("UserAccountServices", "LocalUserAccountServicesConnector"); + config.Configs["UserAccountService"].Set("StorageProvider", "OpenSim.Data.Null.dll"); + config.Configs["UserAccountService"].Set( + "LocalServiceModule", "OpenSim.Services.UserAccountService.dll:UserAccountService"); + + LocalUserAccountServicesConnector userAccountService = new LocalUserAccountServicesConnector(); + userAccountService.Initialise(config); + + return userAccountService; + } + + /// + /// Start a presence service + /// + /// + private static LocalPresenceServicesConnector StartPresenceService() + { + // Unfortunately, some services share data via statics, so we need to null every time to stop interference + // between tests. + // This is a massive non-obvious pita. + NullPresenceData.Instance = null; + + IConfigSource config = new IniConfigSource(); + config.AddConfig("Modules"); + config.AddConfig("PresenceService"); + config.Configs["Modules"].Set("PresenceServices", "LocalPresenceServicesConnector"); + config.Configs["PresenceService"].Set("StorageProvider", "OpenSim.Data.Null.dll"); + config.Configs["PresenceService"].Set( + "LocalServiceModule", "OpenSim.Services.PresenceService.dll:PresenceService"); + + LocalPresenceServicesConnector presenceService = new LocalPresenceServicesConnector(); + presenceService.Initialise(config); + + return presenceService; + } + + private static PhysicsScene StartPhysicsScene() + { + IConfigSource config = new IniConfigSource(); + config.AddConfig("Startup"); + config.Configs["Startup"].Set("physics", "basicphysics"); + + PhysicsScene pScene = new BasicScene(); + INonSharedRegionModule mod = pScene as INonSharedRegionModule; + mod.Initialise(config); + + return pScene; + } + + /// + /// Setup modules for a scene using their default settings. + /// + /// + /// + public static void SetupSceneModules(Scene scene, params object[] modules) + { + SetupSceneModules(scene, new IniConfigSource(), modules); + } + + /// + /// Setup modules for a scene. + /// + /// + /// If called directly, then all the modules must be shared modules. + /// + /// + /// + /// + public static void SetupSceneModules(Scene scene, IConfigSource config, params object[] modules) + { + SetupSceneModules(new Scene[] { scene }, config, modules); + } + + /// + /// Setup modules for a scene using their default settings. + /// + /// + /// + public static void SetupSceneModules(Scene[] scenes, params object[] modules) + { + SetupSceneModules(scenes, new IniConfigSource(), modules); + } + + /// + /// Setup modules for scenes. + /// + /// + /// If called directly, then all the modules must be shared modules. + /// + /// We are emulating here the normal calls made to setup region modules + /// (Initialise(), PostInitialise(), AddRegion, RegionLoaded()). + /// TODO: Need to reuse normal runtime module code. + /// + /// + /// + /// + public static void SetupSceneModules(Scene[] scenes, IConfigSource config, params object[] modules) + { + List newModules = new List(); + foreach (object module in modules) + { + IRegionModuleBase m = (IRegionModuleBase)module; +// Console.WriteLine("MODULE {0}", m.Name); + m.Initialise(config); + newModules.Add(m); + } + + foreach (IRegionModuleBase module in newModules) + { + if (module is ISharedRegionModule) ((ISharedRegionModule)module).PostInitialise(); + } + + foreach (IRegionModuleBase module in newModules) + { + foreach (Scene scene in scenes) + { + module.AddRegion(scene); + scene.AddRegionModule(module.Name, module); + } + } + + // RegionLoaded is fired after all modules have been appropriately added to all scenes + foreach (IRegionModuleBase module in newModules) + foreach (Scene scene in scenes) + module.RegionLoaded(scene); + + foreach (Scene scene in scenes) { scene.SetModuleInterfaces(); } + } + + /// + /// Generate some standard agent connection data. + /// + /// + /// + public static AgentCircuitData GenerateAgentData(UUID agentId) + { + AgentCircuitData acd = GenerateCommonAgentData(); + + acd.AgentID = agentId; + acd.firstname = "testfirstname"; + acd.lastname = "testlastname"; + acd.ServiceURLs = new Dictionary(); + + return acd; + } + + /// + /// Generate some standard agent connection data. + /// + /// + /// + public static AgentCircuitData GenerateAgentData(UserAccount ua) + { + AgentCircuitData acd = GenerateCommonAgentData(); + + acd.AgentID = ua.PrincipalID; + acd.firstname = ua.FirstName; + acd.lastname = ua.LastName; + acd.ServiceURLs = ua.ServiceURLs; + + return acd; + } + + private static AgentCircuitData GenerateCommonAgentData() + { + AgentCircuitData acd = new AgentCircuitData(); + + // XXX: Sessions must be unique, otherwise one presence can overwrite another in NullPresenceData. + acd.SessionID = UUID.Random(); + acd.SecureSessionID = UUID.Random(); + + acd.circuitcode = 123; + acd.BaseFolder = UUID.Zero; + acd.InventoryFolder = UUID.Zero; + acd.startpos = Vector3.Zero; + acd.CapsPath = "http://wibble.com"; + acd.Appearance = new AvatarAppearance(); + + return acd; + } + + /// + /// Add a root agent where the details of the agent connection (apart from the id) are unimportant for the test + /// + /// + /// XXX: Use the version of this method that takes the UserAccount structure wherever possible - this will + /// make the agent circuit data (e.g. first, lastname) consistent with the user account data. + /// + /// + /// + /// + public static ScenePresence AddScenePresence(Scene scene, UUID agentId) + { + return AddScenePresence(scene, GenerateAgentData(agentId)); + } + + /// + /// Add a root agent. + /// + /// + /// + /// + public static ScenePresence AddScenePresence(Scene scene, UserAccount ua) + { + return AddScenePresence(scene, GenerateAgentData(ua)); + } + + /// + /// Add a root agent. + /// + /// + /// This function + /// + /// 1) Tells the scene that an agent is coming. Normally, the login service (local if standalone, from the + /// userserver if grid) would give initial login data back to the client and separately tell the scene that the + /// agent was coming. + /// + /// 2) Connects the agent with the scene + /// + /// This function performs actions equivalent with notifying the scene that an agent is + /// coming and then actually connecting the agent to the scene. The one step missed out is the very first + /// + /// + /// + /// + public static ScenePresence AddScenePresence(Scene scene, AgentCircuitData agentData) + { + return AddScenePresence(scene, new TestClient(agentData, scene), agentData); + } + + /// + /// Add a root agent. + /// + /// + /// This function + /// + /// 1) Tells the scene that an agent is coming. Normally, the login service (local if standalone, from the + /// userserver if grid) would give initial login data back to the client and separately tell the scene that the + /// agent was coming. + /// + /// 2) Connects the agent with the scene + /// + /// This function performs actions equivalent with notifying the scene that an agent is + /// coming and then actually connecting the agent to the scene. The one step missed out is the very first + /// + /// + /// + /// + public static ScenePresence AddScenePresence( + Scene scene, IClientAPI client, AgentCircuitData agentData) + { + // We emulate the proper login sequence here by doing things in four stages + + // Stage 0: login + // We need to punch through to the underlying service because scene will not, correctly, let us call it + // through it's reference to the LPSC + LocalPresenceServicesConnector lpsc = (LocalPresenceServicesConnector)scene.PresenceService; + lpsc.m_PresenceService.LoginAgent(agentData.AgentID.ToString(), agentData.SessionID, agentData.SecureSessionID); + + // Stages 1 & 2 + ScenePresence sp = IntroduceClientToScene(scene, client, agentData, TeleportFlags.ViaLogin); + + // Stage 3: Complete the entrance into the region. This converts the child agent into a root agent. + sp.CompleteMovement(sp.ControllingClient, true); + + return sp; + } + + /// + /// Introduce an agent into the scene by adding a new client. + /// + /// The scene presence added + /// + /// + /// + /// + private static ScenePresence IntroduceClientToScene( + Scene scene, IClientAPI client, AgentCircuitData agentData, TeleportFlags tf) + { + string reason; + + // Stage 1: tell the scene to expect a new user connection + if (!scene.NewUserConnection(agentData, (uint)tf, null, out reason)) + Console.WriteLine("NewUserConnection failed: " + reason); + + // Stage 2: add the new client as a child agent to the scene + scene.AddNewAgent(client, PresenceType.User); + + return scene.GetScenePresence(client.AgentId); + } + + public static ScenePresence AddChildScenePresence(Scene scene, UUID agentId) + { + return AddChildScenePresence(scene, GenerateAgentData(agentId)); + } + + public static ScenePresence AddChildScenePresence(Scene scene, AgentCircuitData acd) + { + acd.child = true; + + // XXX: ViaLogin may not be correct for child agents + TestClient client = new TestClient(acd, scene); + return IntroduceClientToScene(scene, client, acd, TeleportFlags.ViaLogin); + } + + /// + /// Add a test object + /// + /// + /// + public static SceneObjectGroup AddSceneObject(Scene scene) + { + return AddSceneObject(scene, "Test Object", UUID.Random()); + } + + /// + /// Add a test object + /// + /// + /// + /// + /// + public static SceneObjectGroup AddSceneObject(Scene scene, string name, UUID ownerId) + { + SceneObjectGroup so = new SceneObjectGroup(CreateSceneObjectPart(name, UUID.Random(), ownerId)); + + //part.UpdatePrimFlags(false, false, true); + //part.ObjectFlags |= (uint)PrimFlags.Phantom; + + scene.AddNewSceneObject(so, true); + so.InvalidateDeepEffectivePerms(); + + return so; + } + + /// + /// Add a test object + /// + /// + /// + /// The number of parts that should be in the scene object + /// + /// + /// + /// The prefix to be given to part names. This will be suffixed with "Part" + /// (e.g. mynamePart1 for the root part) + /// + /// + /// The hexadecimal last part of the UUID for parts created. A UUID of the form "00000000-0000-0000-0000-{0:XD12}" + /// will be given to the root part, and incremented for each part thereafter. + /// + /// + public static SceneObjectGroup AddSceneObject(Scene scene, int parts, UUID ownerId, string partNamePrefix, int uuidTail) + { + SceneObjectGroup so = CreateSceneObject(parts, ownerId, partNamePrefix, uuidTail); + + scene.AddNewSceneObject(so, false); + so.InvalidateDeepEffectivePerms(); + + return so; + } + + /// + /// Create a scene object part. + /// + /// + /// + /// + /// + public static SceneObjectPart CreateSceneObjectPart(string name, UUID id, UUID ownerId) + { + return new SceneObjectPart( + ownerId, PrimitiveBaseShape.Default, Vector3.Zero, Quaternion.Identity, Vector3.Zero) + { Name = name, UUID = id, Scale = new Vector3(1, 1, 1) }; + } + + /// + /// Create a scene object but do not add it to the scene. + /// + /// + /// UUID always starts at 00000000-0000-0000-0000-000000000001. For some purposes, (e.g. serializing direct + /// to another object's inventory) we do not need a scene unique ID. So it would be better to add the + /// UUID when we actually add an object to a scene rather than on creation. + /// + /// The number of parts that should be in the scene object + /// + /// + public static SceneObjectGroup CreateSceneObject(int parts, UUID ownerId) + { + return CreateSceneObject(parts, ownerId, 0x1); + } + + /// + /// Create a scene object but do not add it to the scene. + /// + /// The number of parts that should be in the scene object + /// + /// + /// The hexadecimal last part of the UUID for parts created. A UUID of the form "00000000-0000-0000-0000-{0:XD12}" + /// will be given to the root part, and incremented for each part thereafter. + /// + /// + public static SceneObjectGroup CreateSceneObject(int parts, UUID ownerId, int uuidTail) + { + return CreateSceneObject(parts, ownerId, "", uuidTail); + } + + /// + /// Create a scene object but do not add it to the scene. + /// + /// + /// The number of parts that should be in the scene object + /// + /// + /// + /// The prefix to be given to part names. This will be suffixed with "Part" + /// (e.g. mynamePart1 for the root part) + /// + /// + /// The hexadecimal last part of the UUID for parts created. A UUID of the form "00000000-0000-0000-0000-{0:XD12}" + /// will be given to the root part, and incremented for each part thereafter. + /// + /// + public static SceneObjectGroup CreateSceneObject(int parts, UUID ownerId, string partNamePrefix, int uuidTail) + { + string rawSogId = string.Format("00000000-0000-0000-0000-{0:X12}", uuidTail); + + SceneObjectGroup sog + = new SceneObjectGroup( + CreateSceneObjectPart(string.Format("{0}Part1", partNamePrefix), new UUID(rawSogId), ownerId)); + + if (parts > 1) + for (int i = 2; i <= parts; i++) + sog.AddPart( + CreateSceneObjectPart( + string.Format("{0}Part{1}", partNamePrefix, i), + new UUID(string.Format("00000000-0000-0000-0000-{0:X12}", uuidTail + i - 1)), + ownerId)); + + return sog; + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Helpers/TaskInventoryHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/TaskInventoryHelpers.cs new file mode 100644 index 00000000000..e3110f677c7 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/TaskInventoryHelpers.cs @@ -0,0 +1,210 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using OpenMetaverse; +using OpenMetaverse.Assets; +using OpenSim.Framework; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Tests.Common +{ + /// + /// Utility functions for carrying out task inventory tests. + /// + /// + public static class TaskInventoryHelpers + { + /// + /// Add a notecard item to the given part. + /// + /// + /// + /// + /// UUID or UUID stem + /// UUID or UUID stem + /// The tex to put in the notecard. + /// The item that was added + public static TaskInventoryItem AddNotecard( + IAssetService assetService, SceneObjectPart part, string itemName, string itemIDStem, string assetIDStem, string text) + { + return AddNotecard( + assetService, part, itemName, TestHelpers.ParseStem(itemIDStem), TestHelpers.ParseStem(assetIDStem), text); + } + + /// + /// Add a notecard item to the given part. + /// + /// + /// + /// + /// + /// + /// The tex to put in the notecard. + /// The item that was added + public static TaskInventoryItem AddNotecard( + IAssetService assetService, SceneObjectPart part, string itemName, UUID itemID, UUID assetID, string text) + { + AssetNotecard nc = new AssetNotecard(); + nc.BodyText = text; + nc.Encode(); + + AssetBase ncAsset + = AssetHelpers.CreateAsset(assetID, AssetType.Notecard, nc.AssetData, UUID.Zero); + assetService.Store(ncAsset); + + TaskInventoryItem ncItem + = new TaskInventoryItem + { Name = itemName, AssetID = assetID, ItemID = itemID, + Type = (int)AssetType.Notecard, InvType = (int)InventoryType.Notecard }; + part.Inventory.AddInventoryItem(ncItem, true); + + return ncItem; + } + + /// + /// Add a simple script to the given part. + /// + /// + /// TODO: Accept input for item and asset IDs to avoid mysterious script failures that try to use any of these + /// functions more than once in a test. + /// + /// + /// + /// The item that was added + public static TaskInventoryItem AddScript(IAssetService assetService, SceneObjectPart part) + { + return AddScript(assetService, part, "scriptItem", "default { state_entry() { llSay(0, \"Hello World\"); } }"); + } + + /// + /// Add a simple script to the given part. + /// + /// + /// TODO: Accept input for item and asset IDs so that we have completely replicatable regression tests rather + /// than a random component. + /// + /// + /// + /// Name of the script to add + /// LSL script source + /// The item that was added + public static TaskInventoryItem AddScript( + IAssetService assetService, SceneObjectPart part, string scriptName, string scriptSource) + { + return AddScript(assetService, part, UUID.Random(), UUID.Random(), scriptName, scriptSource); + } + + /// + /// Add a simple script to the given part. + /// + /// + /// TODO: Accept input for item and asset IDs so that we have completely replicatable regression tests rather + /// than a random component. + /// + /// + /// + /// Item UUID for the script + /// Asset UUID for the script + /// Name of the script to add + /// LSL script source + /// The item that was added + public static TaskInventoryItem AddScript( + IAssetService assetService, SceneObjectPart part, UUID itemId, UUID assetId, string scriptName, string scriptSource) + { + AssetScriptText ast = new AssetScriptText(); + ast.Source = scriptSource; + ast.Encode(); + + AssetBase asset + = AssetHelpers.CreateAsset(assetId, AssetType.LSLText, ast.AssetData, UUID.Zero); + assetService.Store(asset); + TaskInventoryItem item + = new TaskInventoryItem + { Name = scriptName, AssetID = assetId, ItemID = itemId, + Type = (int)AssetType.LSLText, InvType = (int)InventoryType.LSL }; + part.Inventory.AddInventoryItem(item, true); + + return item; + } + + /// + /// Add a scene object item to the given part. + /// + /// + /// TODO: Accept input for item and asset IDs to avoid mysterious script failures that try to use any of these + /// functions more than once in a test. + /// + /// + /// + /// + /// + /// + /// + /// + public static TaskInventoryItem AddSceneObject( + IAssetService assetService, SceneObjectPart sop, string itemName, UUID itemId, SceneObjectGroup soToAdd, UUID soAssetId) + { + AssetBase taskSceneObjectAsset = AssetHelpers.CreateAsset(soAssetId, soToAdd); + assetService.Store(taskSceneObjectAsset); + TaskInventoryItem taskSceneObjectItem + = new TaskInventoryItem + { Name = itemName, + AssetID = taskSceneObjectAsset.FullID, + ItemID = itemId, + OwnerID = soToAdd.OwnerID, + Type = (int)AssetType.Object, + InvType = (int)InventoryType.Object }; + sop.Inventory.AddInventoryItem(taskSceneObjectItem, true); + + return taskSceneObjectItem; + } + + /// + /// Add a scene object item to the given part. + /// + /// + /// TODO: Accept input for item and asset IDs to avoid mysterious script failures that try to use any of these + /// functions more than once in a test. + /// + /// + /// + /// + /// + /// + /// + public static TaskInventoryItem AddSceneObject( + IAssetService assetService, SceneObjectPart sop, string itemName, UUID itemId, UUID userId) + { + SceneObjectGroup taskSceneObject = SceneHelpers.CreateSceneObject(1, userId); + + return TaskInventoryHelpers.AddSceneObject( + assetService, sop, itemName, itemId, taskSceneObject, TestHelpers.ParseTail(0x10)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Helpers/UserAccountHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/UserAccountHelpers.cs new file mode 100644 index 00000000000..e6af34b879f --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/UserAccountHelpers.cs @@ -0,0 +1,160 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using OpenMetaverse; + +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Tests.Common +{ + /// + /// Utility functions for carrying out user profile related tests. + /// + public static class UserAccountHelpers + { +// /// +// /// Create a test user with a standard inventory +// /// +// /// +// /// +// /// Callback to invoke when inventory has been loaded. This is required because +// /// loading may be asynchronous, even on standalone +// /// +// /// +// public static CachedUserInfo CreateUserWithInventory( +// CommunicationsManager commsManager, OnInventoryReceivedDelegate callback) +// { +// UUID userId = UUID.Parse("00000000-0000-0000-0000-000000000099"); +// return CreateUserWithInventory(commsManager, userId, callback); +// } +// +// /// +// /// Create a test user with a standard inventory +// /// +// /// +// /// User ID +// /// +// /// Callback to invoke when inventory has been loaded. This is required because +// /// loading may be asynchronous, even on standalone +// /// +// /// +// public static CachedUserInfo CreateUserWithInventory( +// CommunicationsManager commsManager, UUID userId, OnInventoryReceivedDelegate callback) +// { +// return CreateUserWithInventory(commsManager, "Bill", "Bailey", userId, callback); +// } +// +// /// +// /// Create a test user with a standard inventory +// /// +// /// +// /// First name of user +// /// Last name of user +// /// User ID +// /// +// /// Callback to invoke when inventory has been loaded. This is required because +// /// loading may be asynchronous, even on standalone +// /// +// /// +// public static CachedUserInfo CreateUserWithInventory( +// CommunicationsManager commsManager, string firstName, string lastName, +// UUID userId, OnInventoryReceivedDelegate callback) +// { +// return CreateUserWithInventory(commsManager, firstName, lastName, "troll", userId, callback); +// } +// +// /// +// /// Create a test user with a standard inventory +// /// +// /// +// /// First name of user +// /// Last name of user +// /// Password +// /// User ID +// /// +// /// Callback to invoke when inventory has been loaded. This is required because +// /// loading may be asynchronous, even on standalone +// /// +// /// +// public static CachedUserInfo CreateUserWithInventory( +// CommunicationsManager commsManager, string firstName, string lastName, string password, +// UUID userId, OnInventoryReceivedDelegate callback) +// { +// LocalUserServices lus = (LocalUserServices)commsManager.UserService; +// lus.AddUser(firstName, lastName, password, "bill@bailey.com", 1000, 1000, userId); +// +// CachedUserInfo userInfo = commsManager.UserProfileCacheService.GetUserDetails(userId); +// userInfo.OnInventoryReceived += callback; +// userInfo.FetchInventory(); +// +// return userInfo; +// } + + public static UserAccount CreateUserWithInventory(Scene scene) + { + return CreateUserWithInventory(scene, TestHelpers.ParseTail(99)); + } + + public static UserAccount CreateUserWithInventory(Scene scene, UUID userId) + { + return CreateUserWithInventory(scene, "Bill", "Bailey", userId, "troll"); + } + + public static UserAccount CreateUserWithInventory(Scene scene, int userId) + { + return CreateUserWithInventory(scene, "Bill", "Bailey", TestHelpers.ParseTail(userId), "troll"); + } + + public static UserAccount CreateUserWithInventory( + Scene scene, string firstName, string lastName, UUID userId, string pw) + { + UserAccount ua = new UserAccount(userId) { FirstName = firstName, LastName = lastName }; + CreateUserWithInventory(scene, ua, pw); + return ua; + } + + public static UserAccount CreateUserWithInventory( + Scene scene, string firstName, string lastName, int userId, string pw) + { + UserAccount ua + = new UserAccount(TestHelpers.ParseTail(userId)) { FirstName = firstName, LastName = lastName }; + CreateUserWithInventory(scene, ua, pw); + return ua; + } + + public static void CreateUserWithInventory(Scene scene, UserAccount ua, string pw) + { + // FIXME: This should really be set up by UserAccount itself + ua.ServiceURLs = new Dictionary(); + scene.UserAccountService.StoreUserAccount(ua); + scene.InventoryService.CreateUserInventory(ua.PrincipalID); + scene.AuthenticationService.SetPassword(ua.PrincipalID, pw); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Helpers/UserInventoryHelpers.cs b/Tests/OpenSim.Tests.Common/Helpers/UserInventoryHelpers.cs new file mode 100644 index 00000000000..e18866598c3 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Helpers/UserInventoryHelpers.cs @@ -0,0 +1,374 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.CoreModules.Avatar.Inventory.Archiver; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Tests.Common +{ + /// + /// Utility functions for carrying out user inventory tests. + /// + public static class UserInventoryHelpers + { + public static readonly string PATH_DELIMITER = "/"; + + /// + /// Add an existing scene object as an item in the user's inventory. + /// + /// + /// Will be added to the system Objects folder. + /// + /// + /// + /// + /// + /// The inventory item created. + public static InventoryItemBase AddInventoryItem( + Scene scene, SceneObjectGroup so, int inventoryIdTail, int assetIdTail) + { + return AddInventoryItem( + scene, + so.Name, + TestHelpers.ParseTail(inventoryIdTail), + InventoryType.Object, + AssetHelpers.CreateAsset(TestHelpers.ParseTail(assetIdTail), so), + so.OwnerID); + } + + /// + /// Add an existing scene object as an item in the user's inventory at the given path. + /// + /// + /// + /// + /// + /// The inventory item created. + public static InventoryItemBase AddInventoryItem( + Scene scene, SceneObjectGroup so, int inventoryIdTail, int assetIdTail, string path) + { + return AddInventoryItem( + scene, + so.Name, + TestHelpers.ParseTail(inventoryIdTail), + InventoryType.Object, + AssetHelpers.CreateAsset(TestHelpers.ParseTail(assetIdTail), so), + so.OwnerID, + path); + } + + /// + /// Adds the given item to the existing system folder for its type (e.g. an object will go in the "Objects" + /// folder). + /// + /// + /// + /// + /// + /// The serialized asset for this item + /// + /// + private static InventoryItemBase AddInventoryItem( + Scene scene, string itemName, UUID itemId, InventoryType itemType, AssetBase asset, UUID userId) + { + return AddInventoryItem( + scene, itemName, itemId, itemType, asset, userId, + scene.InventoryService.GetFolderForType(userId, (FolderType)asset.Type).Name); + } + + /// + /// Adds the given item to an inventory folder + /// + /// + /// + /// + /// + /// The serialized asset for this item + /// + /// Existing inventory path at which to add. + /// + private static InventoryItemBase AddInventoryItem( + Scene scene, string itemName, UUID itemId, InventoryType itemType, AssetBase asset, UUID userId, string path) + { + scene.AssetService.Store(asset); + + InventoryItemBase item = new InventoryItemBase(); + item.Name = itemName; + item.AssetID = asset.FullID; + item.ID = itemId; + item.Owner = userId; + item.AssetType = asset.Type; + item.InvType = (int)itemType; + item.BasePermissions = (uint)OpenMetaverse.PermissionMask.All | + (uint)(Framework.PermissionMask.FoldedMask | Framework.PermissionMask.FoldedCopy | Framework.PermissionMask.FoldedModify | Framework.PermissionMask.FoldedTransfer); + item.CurrentPermissions = (uint)OpenMetaverse.PermissionMask.All | + (uint)(Framework.PermissionMask.FoldedMask | Framework.PermissionMask.FoldedCopy | Framework.PermissionMask.FoldedModify | Framework.PermissionMask.FoldedTransfer); + + InventoryFolderBase folder = InventoryArchiveUtils.FindFoldersByPath(scene.InventoryService, userId, path)[0]; + + item.Folder = folder.ID; + scene.AddInventoryItem(item); + + return item; + } + + /// + /// Creates a notecard in the objects folder and specify an item id. + /// + /// + /// + /// + /// + /// + public static InventoryItemBase CreateInventoryItem(Scene scene, string itemName, UUID userId) + { + return CreateInventoryItem(scene, itemName, UUID.Random(), UUID.Random(), userId, InventoryType.Notecard); + } + + /// + /// Creates an item of the given type with an accompanying asset. + /// + /// + /// + /// + /// + /// Type of item to create + /// + public static InventoryItemBase CreateInventoryItem( + Scene scene, string itemName, UUID userId, InventoryType type) + { + return CreateInventoryItem(scene, itemName, UUID.Random(), UUID.Random(), userId, type); + } + + /// + /// Creates a notecard in the objects folder and specify an item id. + /// + /// + /// + /// + /// + /// + /// Type of item to create + /// + public static InventoryItemBase CreateInventoryItem( + Scene scene, string itemName, UUID itemId, UUID assetId, UUID userId, InventoryType itemType) + { + AssetBase asset = null; + + if (itemType == InventoryType.Notecard) + { + asset = AssetHelpers.CreateNotecardAsset(); + asset.CreatorID = userId.ToString(); + } + else if (itemType == InventoryType.Object) + { + asset = AssetHelpers.CreateAsset(assetId, SceneHelpers.CreateSceneObject(1, userId)); + } + else + { + throw new Exception(string.Format("Inventory type {0} not supported", itemType)); + } + + return AddInventoryItem(scene, itemName, itemId, itemType, asset, userId); + } + + /// + /// Create inventory folders starting from the user's root folder. + /// + /// + /// + /// + /// The folders to create. Multiple folders can be specified on a path delimited by the PATH_DELIMITER + /// + /// + /// If true, then folders in the path which already the same name are + /// used. This applies to the terminal folder as well. + /// If false, then all folders in the path are created, even if there is already a folder at a particular + /// level with the same name. + /// + /// + /// The folder created. If the path contains multiple folders then the last one created is returned. + /// Will return null if the root folder could not be found. + /// + public static InventoryFolderBase CreateInventoryFolder( + IInventoryService inventoryService, UUID userId, string path, bool useExistingFolders) + { + return CreateInventoryFolder(inventoryService, userId, UUID.Random(), path, useExistingFolders); + } + + /// + /// Create inventory folders starting from the user's root folder. + /// + /// + /// + /// + /// + /// The folders to create. Multiple folders can be specified on a path delimited by the PATH_DELIMITER + /// + /// + /// If true, then folders in the path which already the same name are + /// used. This applies to the terminal folder as well. + /// If false, then all folders in the path are created, even if there is already a folder at a particular + /// level with the same name. + /// + /// + /// The folder created. If the path contains multiple folders then the last one created is returned. + /// Will return null if the root folder could not be found. + /// + public static InventoryFolderBase CreateInventoryFolder( + IInventoryService inventoryService, UUID userId, UUID folderId, string path, bool useExistingFolders) + { + InventoryFolderBase rootFolder = inventoryService.GetRootFolder(userId); + + if (null == rootFolder) + return null; + + return CreateInventoryFolder(inventoryService, folderId, rootFolder, path, useExistingFolders); + } + + /// + /// Create inventory folders starting from a given parent folder + /// + /// + /// If any stem of the path names folders that already exist then these are not recreated. This includes the + /// final folder. + /// TODO: May need to make it an option to create duplicate folders. + /// + /// + /// ID of the folder to create + /// + /// + /// The folder to create. + /// + /// + /// If true, then folders in the path which already the same name are + /// used. This applies to the terminal folder as well. + /// If false, then all folders in the path are created, even if there is already a folder at a particular + /// level with the same name. + /// + /// + /// The folder created. If the path contains multiple folders then the last one created is returned. + /// + public static InventoryFolderBase CreateInventoryFolder( + IInventoryService inventoryService, UUID folderId, InventoryFolderBase parentFolder, string path, bool useExistingFolders) + { + string[] components = path.Split(new string[] { PATH_DELIMITER }, 2, StringSplitOptions.None); + + InventoryFolderBase folder = null; + + if (useExistingFolders) + folder = InventoryArchiveUtils.FindFolderByPath(inventoryService, parentFolder, components[0]); + + if (folder == null) + { +// Console.WriteLine("Creating folder {0} at {1}", components[0], parentFolder.Name); + + UUID folderIdForCreate; + + if (components.Length > 1) + folderIdForCreate = UUID.Random(); + else + folderIdForCreate = folderId; + + folder + = new InventoryFolderBase( + folderIdForCreate, components[0], parentFolder.Owner, (short)AssetType.Unknown, parentFolder.ID, 0); + + inventoryService.AddFolder(folder); + } +// else +// { +// Console.WriteLine("Found existing folder {0}", folder.Name); +// } + + if (components.Length > 1) + return CreateInventoryFolder(inventoryService, folderId, folder, components[1], useExistingFolders); + else + return folder; + } + + /// + /// Get the inventory folder that matches the path name. If there are multiple folders then only the first + /// is returned. + /// + /// + /// + /// + /// null if no folder matching the path was found + public static InventoryFolderBase GetInventoryFolder(IInventoryService inventoryService, UUID userId, string path) + { + List folders = GetInventoryFolders(inventoryService, userId, path); + + if (folders.Count != 0) + return folders[0]; + else + return null; + } + + /// + /// Get the inventory folders that match the path name. + /// + /// + /// + /// + /// An empty list if no matching folders were found + public static List GetInventoryFolders(IInventoryService inventoryService, UUID userId, string path) + { + return InventoryArchiveUtils.FindFoldersByPath(inventoryService, userId, path); + } + + /// + /// Get the inventory item that matches the path name. If there are multiple items then only the first + /// is returned. + /// + /// + /// + /// + /// null if no item matching the path was found + public static InventoryItemBase GetInventoryItem(IInventoryService inventoryService, UUID userId, string path) + { + return InventoryArchiveUtils.FindItemByPath(inventoryService, userId, path); + } + + /// + /// Get the inventory items that match the path name. + /// + /// + /// + /// + /// An empty list if no matching items were found. + public static List GetInventoryItems(IInventoryService inventoryService, UUID userId, string path) + { + return InventoryArchiveUtils.FindItemsByPath(inventoryService, userId, path); + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/BaseAssetRepository.cs b/Tests/OpenSim.Tests.Common/Mock/BaseAssetRepository.cs new file mode 100644 index 00000000000..d0430ff63a9 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/BaseAssetRepository.cs @@ -0,0 +1,62 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; + +namespace OpenSim.Tests.Common +{ + public class BaseAssetRepository + { + protected Dictionary Assets = new Dictionary(); + + public AssetBase FetchAsset(UUID uuid) + { + if (AssetsExist(new[] { uuid })[0]) + return Assets[uuid]; + else + return null; + } + + public void CreateAsset(AssetBase asset) + { + Assets[asset.FullID] = asset; + } + + public void UpdateAsset(AssetBase asset) + { + CreateAsset(asset); + } + + public bool[] AssetsExist(UUID[] uuids) + { + return Array.ConvertAll(uuids, id => Assets.ContainsKey(id)); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/MockAssetDataPlugin.cs b/Tests/OpenSim.Tests.Common/Mock/MockAssetDataPlugin.cs new file mode 100644 index 00000000000..aaf61e7df21 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/MockAssetDataPlugin.cs @@ -0,0 +1,70 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Data; + +namespace OpenSim.Tests.Common +{ + /// + /// In memory asset data plugin for test purposes. Could be another dll when properly filled out and when the + /// mono addin plugin system starts co-operating with the unit test system. Currently no locking since unit + /// tests are single threaded. + /// + public class MockAssetDataPlugin : BaseAssetRepository, IAssetDataPlugin + { + public string Version { get { return "0"; } } + public string Name { get { return "MockAssetDataPlugin"; } } + + public void Initialise() {} + public void Initialise(string connect) {} + public void Dispose() {} + + private readonly List assets = new List(); + + public AssetBase GetAsset(UUID uuid) + { + return assets.Find(x=>x.FullID == uuid); + } + + public bool StoreAsset(AssetBase asset) + { + assets.Add(asset); + return true; + } + + public List FetchAssetMetadataSet(int start, int count) { return new List(count); } + + public bool Delete(string id) + { + return false; + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/MockGroupsServicesConnector.cs b/Tests/OpenSim.Tests.Common/Mock/MockGroupsServicesConnector.cs new file mode 100644 index 00000000000..9a46081be89 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/MockGroupsServicesConnector.cs @@ -0,0 +1,398 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using Mono.Addins; +using Nini.Config; +using OpenMetaverse; + +using OpenSim.Data; +using OpenSim.Data.Null; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.OptionalModules.Avatar.XmlRpcGroups; + +namespace OpenSim.Tests.Common +{ + [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule")] + public class MockGroupsServicesConnector : ISharedRegionModule, IGroupsServicesConnector + { + IXGroupData m_data = new NullXGroupData(null, null); + + public string Name + { + get { return "MockGroupsServicesConnector"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Initialise(IConfigSource config) + { + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + scene.RegisterModuleInterface(this); + } + + public void RemoveRegion(Scene scene) + { + } + + public void RegionLoaded(Scene scene) + { + } + + public void PostInitialise() + { + } + + public UUID CreateGroup(UUID requestingAgentID, string name, string charter, bool showInList, UUID insigniaID, + int membershipFee, bool openEnrollment, bool allowPublish, + bool maturePublish, UUID founderID) + { + XGroup group = new XGroup() + { + groupID = UUID.Random(), + ownerRoleID = UUID.Random(), + name = name, + charter = charter, + showInList = showInList, + insigniaID = insigniaID, + membershipFee = membershipFee, + openEnrollment = openEnrollment, + allowPublish = allowPublish, + maturePublish = maturePublish, + founderID = founderID, + everyonePowers = (ulong)XmlRpcGroupsServicesConnectorModule.DefaultEveryonePowers, + ownersPowers = (ulong)XmlRpcGroupsServicesConnectorModule.DefaultOwnerPowers + }; + + if (m_data.StoreGroup(group)) + { + return group.groupID; + } + else + { + return UUID.Zero; + } + } + + public void UpdateGroup(UUID requestingAgentID, UUID groupID, string charter, bool showInList, + UUID insigniaID, int membershipFee, bool openEnrollment, + bool allowPublish, bool maturePublish) + { + } + + public void AddGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID, string name, string description, + string title, ulong powers) + { + } + + public void RemoveGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID) + { + } + + public void UpdateGroupRole(UUID requestingAgentID, UUID groupID, UUID roleID, string name, string description, + string title, ulong powers) + { + } + + private XGroup GetXGroup(UUID groupID, string name) + { + XGroup group = m_data.GetGroup(groupID); + return group; + } + + public GroupRecord GetGroupRecord(UUID requestingAgentID, UUID groupID, string groupName) + { + XGroup xg = GetXGroup(groupID, groupName); + + if (xg == null) + return null; + + GroupRecord gr = new GroupRecord() + { + GroupID = xg.groupID, + GroupName = xg.name, + AllowPublish = xg.allowPublish, + MaturePublish = xg.maturePublish, + Charter = xg.charter, + FounderID = xg.founderID, + // FIXME: group picture storage location unknown + MembershipFee = xg.membershipFee, + OpenEnrollment = xg.openEnrollment, + OwnerRoleID = xg.ownerRoleID, + ShowInList = xg.showInList + }; + + return gr; + } + + public GroupProfileData GetMemberGroupProfile(UUID requestingAgentID, UUID GroupID, UUID AgentID) + { + return default(GroupProfileData); + } + + public void SetAgentActiveGroup(UUID requestingAgentID, UUID AgentID, UUID GroupID) + { + } + + public void SetAgentActiveGroupRole(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID) + { + } + + public void SetAgentGroupInfo(UUID requestingAgentID, UUID agentID, UUID groupID, bool acceptNotices, bool listInProfile) + { + XGroup group = GetXGroup(groupID, null); + + if (group == null) + return; + + XGroupMember xgm = null; + if (!group.members.TryGetValue(agentID, out xgm)) + return; + + xgm.acceptNotices = acceptNotices; + xgm.listInProfile = listInProfile; + + m_data.StoreGroup(group); + } + + public void AddAgentToGroupInvite(UUID requestingAgentID, UUID inviteID, UUID groupID, UUID roleID, UUID agentID) + { + } + + public GroupInviteInfo GetAgentToGroupInvite(UUID requestingAgentID, UUID inviteID) + { + return null; + } + + public void RemoveAgentToGroupInvite(UUID requestingAgentID, UUID inviteID) + { + } + + public void AddAgentToGroup(UUID requestingAgentID, UUID agentID, UUID groupID, UUID roleID) + { + XGroup group = GetXGroup(groupID, null); + + if (group == null) + return; + + XGroupMember groupMember = new XGroupMember() + { + agentID = agentID, + groupID = groupID, + roleID = roleID + }; + + group.members[agentID] = groupMember; + + m_data.StoreGroup(group); + } + + public void RemoveAgentFromGroup(UUID requestingAgentID, UUID AgentID, UUID GroupID) + { + } + + public void AddAgentToGroupRole(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID) + { + } + + public void RemoveAgentFromGroupRole(UUID requestingAgentID, UUID AgentID, UUID GroupID, UUID RoleID) + { + } + + public List FindGroups(UUID requestingAgentID, string search) + { + return null; + } + + public GroupMembershipData GetAgentGroupMembership(UUID requestingAgentID, UUID AgentID, UUID GroupID) + { + return null; + } + + public GroupMembershipData GetAgentActiveMembership(UUID requestingAgentID, UUID AgentID) + { + return null; + } + + public List GetAgentGroupMemberships(UUID requestingAgentID, UUID AgentID) + { + return new List(); + } + + public List GetAgentGroupRoles(UUID requestingAgentID, UUID AgentID, UUID GroupID) + { + return null; + } + + public List GetGroupRoles(UUID requestingAgentID, UUID GroupID) + { + return null; + } + + public List GetGroupMembers(UUID requestingAgentID, UUID groupID) + { + List groupMembers = new List(); + + XGroup group = GetXGroup(groupID, null); + + if (group == null) + return groupMembers; + + foreach (XGroupMember xgm in group.members.Values) + { + GroupMembersData gmd = new GroupMembersData(); + gmd.AgentID = xgm.agentID; + gmd.IsOwner = group.founderID == gmd.AgentID; + gmd.AcceptNotices = xgm.acceptNotices; + gmd.ListInProfile = xgm.listInProfile; + + groupMembers.Add(gmd); + } + + return groupMembers; + } + + public List GetGroupRoleMembers(UUID requestingAgentID, UUID GroupID) + { + return null; + } + + public List GetGroupNotices(UUID requestingAgentID, UUID groupID) + { + XGroup group = GetXGroup(groupID, null); + + if (group == null) + return null; + + List notices = new List(); + + foreach (XGroupNotice notice in group.notices.Values) + { + GroupNoticeData gnd = new GroupNoticeData() + { + NoticeID = notice.noticeID, + Timestamp = notice.timestamp, + FromName = notice.fromName, + Subject = notice.subject, + HasAttachment = notice.hasAttachment, + AssetType = (byte)notice.assetType + }; + + notices.Add(gnd); + } + + return notices; + } + + public GroupNoticeInfo GetGroupNotice(UUID requestingAgentID, UUID noticeID) + { + // Yes, not an efficient way to do it. + Dictionary groups = m_data.GetGroups(); + + foreach (XGroup group in groups.Values) + { + if (group.notices.ContainsKey(noticeID)) + { + XGroupNotice n = group.notices[noticeID]; + + GroupNoticeInfo gni = new GroupNoticeInfo(); + gni.GroupID = n.groupID; + gni.Message = n.message; + gni.BinaryBucket = n.binaryBucket; + gni.noticeData.NoticeID = n.noticeID; + gni.noticeData.Timestamp = n.timestamp; + gni.noticeData.FromName = n.fromName; + gni.noticeData.Subject = n.subject; + gni.noticeData.HasAttachment = n.hasAttachment; + gni.noticeData.AssetType = (byte)n.assetType; + + return gni; + } + } + + return null; + } + + public void AddGroupNotice(UUID requestingAgentID, UUID groupID, UUID noticeID, string fromName, string subject, string message, byte[] binaryBucket) + { + XGroup group = GetXGroup(groupID, null); + + if (group == null) + return; + + XGroupNotice groupNotice = new XGroupNotice() + { + groupID = groupID, + noticeID = noticeID, + fromName = fromName, + subject = subject, + message = message, + timestamp = (uint)Util.UnixTimeSinceEpoch(), + hasAttachment = false, + assetType = 0, + binaryBucket = binaryBucket + }; + + group.notices[noticeID] = groupNotice; + + m_data.StoreGroup(group); + } + + public void ResetAgentGroupChatSessions(UUID agentID) + { + } + + public bool hasAgentBeenInvitedToGroupChatSession(UUID agentID, UUID groupID) + { + return false; + } + + public bool hasAgentDroppedGroupChatSession(UUID agentID, UUID groupID) + { + return false; + } + + public void AgentDroppedFromGroupChatSession(UUID agentID, UUID groupID) + { + } + + public void AgentInvitedToGroupChatSession(UUID agentID, UUID groupID) + { + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/MockRegionDataPlugin.cs b/Tests/OpenSim.Tests.Common/Mock/MockRegionDataPlugin.cs new file mode 100644 index 00000000000..85abda3f767 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/MockRegionDataPlugin.cs @@ -0,0 +1,373 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Data.Null +{ + public class NullDataService : ISimulationDataService + { + private NullDataStore m_store; + + public NullDataService() + { + m_store = new NullDataStore(); + } + + public void StoreObject(SceneObjectGroup obj, UUID regionUUID) + { + m_store.StoreObject(obj, regionUUID); + } + + public void RemoveObject(UUID uuid, UUID regionUUID) + { + m_store.RemoveObject(uuid, regionUUID); + } + + public void StorePrimInventory(UUID primID, ICollection items) + { + m_store.StorePrimInventory(primID, items); + } + + public List LoadObjects(UUID regionUUID) + { + return m_store.LoadObjects(regionUUID); + } + + public void StoreTerrain(double[,] terrain, UUID regionID) + { + m_store.StoreTerrain(terrain, regionID); + } + + public void StoreTerrain(TerrainData terrain, UUID regionID) + { + m_store.StoreTerrain(terrain, regionID); + } + + public void StoreBakedTerrain(TerrainData terrain, UUID regionID) + { + m_store.StoreBakedTerrain(terrain, regionID); + } + + public double[,] LoadTerrain(UUID regionID) + { + return m_store.LoadTerrain(regionID); + } + + public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ) + { + return m_store.LoadTerrain(regionID, pSizeX, pSizeY, pSizeZ); + } + + public TerrainData LoadBakedTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ) + { + return m_store.LoadBakedTerrain(regionID, pSizeX, pSizeY, pSizeZ); + } + + public void StoreLandObject(ILandObject Parcel) + { + m_store.StoreLandObject(Parcel); + } + + public void RemoveLandObject(UUID globalID) + { + m_store.RemoveLandObject(globalID); + } + + public List LoadLandObjects(UUID regionUUID) + { + return m_store.LoadLandObjects(regionUUID); + } + + public void StoreRegionSettings(RegionSettings rs) + { + m_store.StoreRegionSettings(rs); + } + + public RegionSettings LoadRegionSettings(UUID regionUUID) + { + return m_store.LoadRegionSettings(regionUUID); + } + + public string LoadRegionEnvironmentSettings(UUID regionUUID) + { + return m_store.LoadRegionEnvironmentSettings(regionUUID); + } + + public void StoreRegionEnvironmentSettings(UUID regionUUID, string settings) + { + m_store.StoreRegionEnvironmentSettings(regionUUID, settings); + } + + public void RemoveRegionEnvironmentSettings(UUID regionUUID) + { + m_store.RemoveRegionEnvironmentSettings(regionUUID); + } + + public UUID[] GetObjectIDs(UUID regionID) + { + return new UUID[0]; + } + + public void SaveExtra(UUID regionID, string name, string value) + { + } + + public void RemoveExtra(UUID regionID, string name) + { + } + + public Dictionary GetExtra(UUID regionID) + { + return null; + } + } + + /// + /// Mock region data plugin. This obeys the api contract for persistence but stores everything in memory, so that + /// tests can check correct persistence. + /// + public class NullDataStore : ISimulationDataStore + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + protected Dictionary m_regionSettings = new Dictionary(); + protected Dictionary m_sceneObjectParts = new Dictionary(); + protected Dictionary> m_primItems + = new Dictionary>(); + protected Dictionary m_terrains = new Dictionary(); + protected Dictionary m_bakedterrains = new Dictionary(); + protected Dictionary m_landData = new Dictionary(); + + public void Initialise(string dbfile) + { + return; + } + + public void Dispose() + { + } + + public void StoreRegionSettings(RegionSettings rs) + { + m_regionSettings[rs.RegionUUID] = rs; + } + + #region Environment Settings + public string LoadRegionEnvironmentSettings(UUID regionUUID) + { + //This connector doesn't support the Environment module yet + return string.Empty; + } + + public void StoreRegionEnvironmentSettings(UUID regionUUID, string settings) + { + //This connector doesn't support the Environment module yet + } + + public void RemoveRegionEnvironmentSettings(UUID regionUUID) + { + //This connector doesn't support the Environment module yet + } + #endregion + + public RegionSettings LoadRegionSettings(UUID regionUUID) + { + RegionSettings rs = null; + m_regionSettings.TryGetValue(regionUUID, out rs); + + if (rs == null) + rs = new RegionSettings(); + + return rs; + } + + public void StoreObject(SceneObjectGroup obj, UUID regionUUID) + { + // We can't simply store groups here because on delinking, OpenSim will not update the original group + // directly. Rather, the newly delinked parts will be updated to be in their own scene object group + // Therefore, we need to store parts rather than groups. + foreach (SceneObjectPart prim in obj.Parts) + { +// m_log.DebugFormat( +// "[MOCK REGION DATA PLUGIN]: Storing part {0} {1} in object {2} {3} in region {4}", +// prim.Name, prim.UUID, obj.Name, obj.UUID, regionUUID); + + m_sceneObjectParts[prim.UUID] = prim; + } + } + + public void RemoveObject(UUID obj, UUID regionUUID) + { + // All parts belonging to the object with the uuid are removed. + List parts = new List(m_sceneObjectParts.Values); + foreach (SceneObjectPart part in parts) + { + if (part.ParentGroup.UUID == obj) + { +// m_log.DebugFormat( +// "[MOCK REGION DATA PLUGIN]: Removing part {0} {1} as part of object {2} from {3}", +// part.Name, part.UUID, obj, regionUUID); + m_sceneObjectParts.Remove(part.UUID); + } + } + } + + public void StorePrimInventory(UUID primID, ICollection items) + { + m_primItems[primID] = items; + } + + public List LoadObjects(UUID regionUUID) + { + Dictionary objects = new Dictionary(); + + // Create all of the SOGs from the root prims first + foreach (SceneObjectPart prim in m_sceneObjectParts.Values) + { + if (prim.IsRoot) + { +// m_log.DebugFormat( +// "[MOCK REGION DATA PLUGIN]: Loading root part {0} {1} in {2}", prim.Name, prim.UUID, regionUUID); + objects[prim.UUID] = new SceneObjectGroup(prim); + } + } + + // Add all of the children objects to the SOGs + foreach (SceneObjectPart prim in m_sceneObjectParts.Values) + { + SceneObjectGroup sog; + if (prim.UUID != prim.ParentUUID) + { + if (objects.TryGetValue(prim.ParentUUID, out sog)) + { + int originalLinkNum = prim.LinkNum; + + sog.AddPart(prim); + + // SceneObjectGroup.AddPart() tries to be smart and automatically set the LinkNum. + // We override that here + if (originalLinkNum != 0) + prim.LinkNum = originalLinkNum; + } + else + { +// m_log.WarnFormat( +// "[MOCK REGION DATA PLUGIN]: Database contains an orphan child prim {0} {1} in region {2} pointing to missing parent {3}. This prim will not be loaded.", +// prim.Name, prim.UUID, regionUUID, prim.ParentUUID); + } + } + } + + // TODO: Load items. This is assymetric - we store items as a separate method but don't retrieve them that + // way! + + return new List(objects.Values); + } + + public void StoreTerrain(TerrainData ter, UUID regionID) + { + m_terrains[regionID] = ter; + } + + public void StoreBakedTerrain(TerrainData ter, UUID regionID) + { + m_bakedterrains[regionID] = ter; + } + + public void StoreTerrain(double[,] ter, UUID regionID) + { + m_terrains[regionID] = new TerrainData(ter); + } + + public TerrainData LoadTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ) + { + if (m_terrains.ContainsKey(regionID)) + return m_terrains[regionID]; + else + return null; + } + + public TerrainData LoadBakedTerrain(UUID regionID, int pSizeX, int pSizeY, int pSizeZ) + { + if (m_bakedterrains.ContainsKey(regionID)) + return m_bakedterrains[regionID]; + else + return null; + } + + public double[,] LoadTerrain(UUID regionID) + { + if (m_terrains.ContainsKey(regionID)) + return m_terrains[regionID].GetDoubles(); + else + return null; + } + + public void RemoveLandObject(UUID globalID) + { + if (m_landData.ContainsKey(globalID)) + m_landData.Remove(globalID); + } + + public void StoreLandObject(ILandObject land) + { + m_landData[land.LandData.GlobalID] = land.LandData; + } + + public List LoadLandObjects(UUID regionUUID) + { + return new List(m_landData.Values); + } + + public void Shutdown() + { + } + + public UUID[] GetObjectIDs(UUID regionID) + { + return new UUID[0]; + } + + public void SaveExtra(UUID regionID, string name, string value) + { + } + + public void RemoveExtra(UUID regionID, string name) + { + } + + public Dictionary GetExtra(UUID regionID) + { + return null; + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/MockScriptEngine.cs b/Tests/OpenSim.Tests.Common/Mock/MockScriptEngine.cs new file mode 100644 index 00000000000..93897c35d43 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/MockScriptEngine.cs @@ -0,0 +1,298 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using Nini.Config; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.ScriptEngine.Interfaces; +using OpenSim.Region.ScriptEngine.Shared; + +namespace OpenSim.Tests.Common +{ + public class MockScriptEngine : INonSharedRegionModule, IScriptModule, IScriptEngine + { + public IConfigSource ConfigSource { get; private set; } + + public IConfig Config { get; private set; } + + private Scene m_scene; + + /// + /// Expose posted events to tests. + /// + public Dictionary> PostedEvents { get; private set; } + + /// + /// A very primitive way of hooking text cose to a posed event. + /// + /// + /// May be replaced with something that uses more original code in the future. + /// + public event Action PostEventHook; + + public void Initialise(IConfigSource source) + { + ConfigSource = source; + + // Can set later on if required + Config = new IniConfig("MockScriptEngine", ConfigSource); + + PostedEvents = new Dictionary>(); + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + m_scene = scene; + + m_scene.StackModuleInterface(this); + } + + public void RemoveRegion(Scene scene) + { + } + + public void RegionLoaded(Scene scene) + { + } + + public string Name { get { return "Mock Script Engine"; } } + public string ScriptEngineName { get { return Name; } } + + public Type ReplaceableInterface { get { return null; } } + +#pragma warning disable 0067 + public event ScriptRemoved OnScriptRemoved; + public event ObjectRemoved OnObjectRemoved; +#pragma warning restore 0067 + + public string GetXMLState (UUID itemID) + { + throw new System.NotImplementedException (); + } + + public bool SetXMLState(UUID itemID, string xml) + { + throw new System.NotImplementedException (); + } + + public void CancelScriptEvent(UUID itemID, string eventName) + { + + } + + public bool PostScriptEvent(UUID itemID, string name, object[] args) + { +// Console.WriteLine("Posting event {0} for {1}", name, itemID); + + return PostScriptEvent(itemID, new EventParams(name, args, null)); + } + + public bool PostScriptEvent(UUID itemID, EventParams evParams) + { + List eventsForItem; + + if (!PostedEvents.ContainsKey(itemID)) + { + eventsForItem = new List(); + PostedEvents.Add(itemID, eventsForItem); + } + else + { + eventsForItem = PostedEvents[itemID]; + } + + eventsForItem.Add(evParams); + + if (PostEventHook != null) + PostEventHook(itemID, evParams); + + return true; + } + + public bool PostObjectEvent(uint localID, EventParams evParams) + { + return PostObjectEvent(m_scene.GetSceneObjectPart(localID), evParams); + } + + public bool PostObjectEvent(UUID itemID, string name, object[] args) + { + return PostObjectEvent(m_scene.GetSceneObjectPart(itemID), new EventParams(name, args, null)); + } + + private bool PostObjectEvent(SceneObjectPart part, EventParams evParams) + { + foreach (TaskInventoryItem item in part.Inventory.GetInventoryItems(InventoryType.LSL)) + PostScriptEvent(item.ItemID, evParams); + + return true; + } + + public bool SuspendScript(UUID itemID) + { + throw new System.NotImplementedException (); + } + + public bool ResumeScript(UUID itemID) + { + throw new System.NotImplementedException (); + } + + public ArrayList GetScriptErrors(UUID itemID) + { + throw new System.NotImplementedException (); + } + + public bool HasScript(UUID itemID, out bool running) + { + throw new System.NotImplementedException (); + } + + public bool GetScriptState(UUID itemID) + { + throw new System.NotImplementedException (); + } + + public void SaveAllState() + { + throw new System.NotImplementedException (); + } + + public void StartProcessing() + { + throw new System.NotImplementedException (); + } + + public float GetScriptExecutionTime(List itemIDs) + { + return 0; + } + + public int GetScriptsMemory(List itemIDs) + { + return 0; + } + + public Dictionary GetObjectScriptsExecutionTimes() + { + throw new System.NotImplementedException (); + } + + public IScriptWorkItem QueueEventHandler(object parms) + { + throw new System.NotImplementedException (); + } + + public DetectParams GetDetectParams(UUID item, int number) + { + throw new System.NotImplementedException (); + } + + public void SetMinEventDelay(UUID itemID, double delay) + { + throw new System.NotImplementedException (); + } + + public int GetStartParameter(UUID itemID) + { + throw new System.NotImplementedException (); + } + + public void SetScriptState(UUID itemID, bool state, bool self) + { + throw new System.NotImplementedException (); + } + + public void SetState(UUID itemID, string newState) + { + throw new System.NotImplementedException (); + } + + public void ApiResetScript(UUID itemID) + { + throw new System.NotImplementedException (); + } + + public void ResetScript (UUID itemID) + { + throw new System.NotImplementedException (); + } + + public IScriptApi GetApi(UUID itemID, string name) + { + throw new System.NotImplementedException (); + } + + public Scene World { get { return m_scene; } } + + public IScriptModule ScriptModule { get { return this; } } + + public string ScriptEnginePath { get { throw new System.NotImplementedException (); }} + + public string ScriptClassName { get { throw new System.NotImplementedException (); } } + + public string ScriptBaseClassName { get { throw new System.NotImplementedException (); } } + + public string[] ScriptReferencedAssemblies { get { throw new System.NotImplementedException (); } } + + public ParameterInfo[] ScriptBaseClassParameters { get { throw new System.NotImplementedException (); } } + + IConfig IScriptEngine.Config => throw new NotImplementedException(); + + IConfigSource IScriptEngine.ConfigSource => throw new NotImplementedException(); + + public void ClearPostedEvents() + { + PostedEvents.Clear(); + } + + public void SleepScript(UUID itemID, int delay) + { + } + + public ICollection GetTopObjectStats(float mintime, int minmemory, out float totaltime, out float totalmemory) + { + totaltime = 0; + totalmemory = 0; + return null; + } + + public bool PostObjectLinksetDataEvent(uint localID, int action, ReadOnlySpan name, ReadOnlySpan value) + { + throw new NotImplementedException(); + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/TestClient.cs b/Tests/OpenSim.Tests.Common/Mock/TestClient.cs new file mode 100644 index 00000000000..2c7c1c2c54c --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestClient.cs @@ -0,0 +1,1410 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Net; + +using OpenMetaverse; +using OpenMetaverse.Packets; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Framework.Client; + +namespace OpenSim.Tests.Common +{ + public class TestClient : IClientAPI, IClientCore + { + EventWaitHandle wh = new EventWaitHandle (false, EventResetMode.AutoReset, "Crossing"); + + private Scene m_scene; + + // Properties so that we can get at received data for test purposes + public List ReceivedKills { get; private set; } + public List ReceivedOfflineNotifications { get; private set; } + public List ReceivedOnlineNotifications { get; private set; } + public List ReceivedFriendshipTerminations { get; private set; } + + public List SentImageDataPackets { get; private set; } + public List SentImagePacketPackets { get; private set; } + public List SentImageNotInDatabasePackets { get; private set; } + + // Test client specific events - for use by tests to implement some IClientAPI behaviour. + public event Action OnReceivedMoveAgentIntoRegion; + public event Action OnTestClientInformClientOfNeighbour; + public event TestClientOnSendRegionTeleportDelegate OnTestClientSendRegionTeleport; + + public event Action OnReceivedEntityUpdate; + + public event OnReceivedChatMessageDelegate OnReceivedChatMessage; + public event Action OnReceivedInstantMessage; + + public event Action OnReceivedSendRebakeAvatarTextures; + + public delegate void TestClientOnSendRegionTeleportDelegate( + ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, + uint locationID, uint flags, string capsURL); + + public delegate void OnReceivedChatMessageDelegate( + string message, byte type, Vector3 fromPos, string fromName, + UUID fromAgentID, UUID ownerID, byte source, byte audible); + + +// disable warning: public events, part of the public API +#pragma warning disable 67 + + public event Action OnLogout; + public event ObjectPermissions OnObjectPermissions; + + public event MoneyTransferRequest OnMoneyTransferRequest; + public event ParcelBuy OnParcelBuy; + public event Action OnConnectionClosed; + public event MoveItemsAndLeaveCopy OnMoveItemsAndLeaveCopy; + public event ImprovedInstantMessage OnInstantMessage; + public event ChatMessage OnChatFromClient; + public event TextureRequest OnRequestTexture; + public event RezObject OnRezObject; + public event ModifyTerrain OnModifyTerrain; + public event BakeTerrain OnBakeTerrain; + public event SetAppearance OnSetAppearance; + public event AvatarNowWearing OnAvatarNowWearing; + public event RezSingleAttachmentFromInv OnRezSingleAttachmentFromInv; + public event RezMultipleAttachmentsFromInv OnRezMultipleAttachmentsFromInv; + public event UUIDNameRequest OnDetachAttachmentIntoInv; + public event ObjectAttach OnObjectAttach; + public event ObjectDeselect OnObjectDetach; + public event ObjectDrop OnObjectDrop; + public event StartAnim OnStartAnim; + public event StopAnim OnStopAnim; + public event ChangeAnim OnChangeAnim; + public event LinkObjects OnLinkObjects; + public event DelinkObjects OnDelinkObjects; + public event RequestMapBlocks OnRequestMapBlocks; + public event RequestMapName OnMapNameRequest; + public event TeleportLocationRequest OnTeleportLocationRequest; + public event TeleportLandmarkRequest OnTeleportLandmarkRequest; + public event TeleportCancel OnTeleportCancel; + public event DisconnectUser OnDisconnectUser; + public event RequestAvatarProperties OnRequestAvatarProperties; + public event SetAlwaysRun OnSetAlwaysRun; + + public event DeRezObject OnDeRezObject; + public event RezRestoreToWorld OnRezRestoreToWorld; + public event Action OnRegionHandShakeReply; + public event GenericCall1 OnRequestWearables; + public event Action OnCompleteMovementToRegion; + public event UpdateAgent OnPreAgentUpdate; + public event UpdateAgent OnAgentUpdate; + public event UpdateAgent OnAgentCameraUpdate; + public event AgentRequestSit OnAgentRequestSit; + public event AgentSit OnAgentSit; + public event AvatarPickerRequest OnAvatarPickerRequest; + public event Action OnRequestAvatarsData; + public event AddNewPrim OnAddPrim; + public event RequestGodlikePowers OnRequestGodlikePowers; + public event GodKickUser OnGodKickUser; + public event ObjectDuplicate OnObjectDuplicate; + public event GrabObject OnGrabObject; + public event DeGrabObject OnDeGrabObject; + public event MoveObject OnGrabUpdate; + public event SpinStart OnSpinStart; + public event SpinObject OnSpinUpdate; + public event SpinStop OnSpinStop; + public event ViewerEffectEventHandler OnViewerEffect; + + public event AgentDataUpdate OnAgentDataUpdateRequest; + public event TeleportLocationRequest OnSetStartLocationRequest; + + public event UpdateShape OnUpdatePrimShape; + public event ObjectExtraParams OnUpdateExtraParams; + public event RequestObjectPropertiesFamily OnRequestObjectPropertiesFamily; + public event ObjectSelect OnObjectSelect; + public event ObjectRequest OnObjectRequest; + public event GenericCall7 OnObjectDescription; + public event GenericCall7 OnObjectName; + public event GenericCall7 OnObjectClickAction; + public event GenericCall7 OnObjectMaterial; + public event UpdatePrimFlags OnUpdatePrimFlags; + public event UpdatePrimTexture OnUpdatePrimTexture; + public event ClientChangeObject onClientChangeObject; + public event UpdateVector OnUpdatePrimGroupPosition; + public event UpdateVector OnUpdatePrimSinglePosition; + public event UpdatePrimRotation OnUpdatePrimGroupRotation; + public event UpdatePrimSingleRotation OnUpdatePrimSingleRotation; + public event UpdatePrimSingleRotationPosition OnUpdatePrimSingleRotationPosition; + public event UpdatePrimGroupRotation OnUpdatePrimGroupMouseRotation; + public event UpdateVector OnUpdatePrimScale; + public event UpdateVector OnUpdatePrimGroupScale; + public event StatusChange OnChildAgentStatus; + public event GenericCall2 OnStopMovement; + public event Action OnRemoveAvatar; + + public event CreateNewInventoryItem OnCreateNewInventoryItem; + public event LinkInventoryItem OnLinkInventoryItem; + public event CreateInventoryFolder OnCreateNewInventoryFolder; + public event UpdateInventoryFolder OnUpdateInventoryFolder; + public event MoveInventoryFolder OnMoveInventoryFolder; + public event RemoveInventoryFolder OnRemoveInventoryFolder; + public event RemoveInventoryItem OnRemoveInventoryItem; + public event FetchInventoryDescendents OnFetchInventoryDescendents; + public event PurgeInventoryDescendents OnPurgeInventoryDescendents; + public event FetchInventory OnFetchInventory; + public event RequestTaskInventory OnRequestTaskInventory; + public event UpdateInventoryItem OnUpdateInventoryItem; + public event CopyInventoryItem OnCopyInventoryItem; + public event MoveInventoryItem OnMoveInventoryItem; + public event UDPAssetUploadRequest OnAssetUploadRequest; + public event RequestTerrain OnRequestTerrain; + public event RequestTerrain OnUploadTerrain; + public event XferReceive OnXferReceive; + public event RequestXfer OnRequestXfer; + public event ConfirmXfer OnConfirmXfer; + public event AbortXfer OnAbortXfer; + public event RezScript OnRezScript; + public event UpdateTaskInventory OnUpdateTaskInventory; + public event MoveTaskInventory OnMoveTaskItem; + public event RemoveTaskInventory OnRemoveTaskItem; + public event RequestAsset OnRequestAsset; + public event GenericMessage OnGenericMessage; + public event UUIDNameRequest OnNameFromUUIDRequest; + public event UUIDNameRequest OnUUIDGroupNameRequest; + + public event ParcelPropertiesRequest OnParcelPropertiesRequest; + public event ParcelDivideRequest OnParcelDivideRequest; + public event ParcelJoinRequest OnParcelJoinRequest; + public event ParcelPropertiesUpdateRequest OnParcelPropertiesUpdateRequest; + public event ParcelAbandonRequest OnParcelAbandonRequest; + public event ParcelGodForceOwner OnParcelGodForceOwner; + public event ParcelReclaim OnParcelReclaim; + public event ParcelReturnObjectsRequest OnParcelReturnObjectsRequest; + public event ParcelAccessListRequest OnParcelAccessListRequest; + public event ParcelAccessListUpdateRequest OnParcelAccessListUpdateRequest; + public event ParcelSelectObjects OnParcelSelectObjects; + public event ParcelObjectOwnerRequest OnParcelObjectOwnerRequest; + public event ParcelDeedToGroup OnParcelDeedToGroup; + public event ObjectDeselect OnObjectDeselect; + public event RegionInfoRequest OnRegionInfoRequest; + public event EstateCovenantRequest OnEstateCovenantRequest; + public event EstateChangeInfo OnEstateChangeInfo; + public event EstateManageTelehub OnEstateManageTelehub; + public event CachedTextureRequest OnCachedTextureRequest; + + public event ObjectDuplicateOnRay OnObjectDuplicateOnRay; + + public event FriendActionDelegate OnApproveFriendRequest; + public event FriendActionDelegate OnDenyFriendRequest; + public event FriendshipTermination OnTerminateFriendship; + public event GrantUserFriendRights OnGrantUserRights; + + public event EconomyDataRequest OnEconomyDataRequest; + public event MoneyBalanceRequest OnMoneyBalanceRequest; + public event UpdateAvatarProperties OnUpdateAvatarProperties; + + public event ObjectIncludeInSearch OnObjectIncludeInSearch; + public event UUIDNameRequest OnTeleportHomeRequest; + + public event ScriptAnswer OnScriptAnswer; + public event RequestPayPrice OnRequestPayPrice; + public event ObjectSaleInfo OnObjectSaleInfo; + public event ObjectBuy OnObjectBuy; + public event BuyObjectInventory OnBuyObjectInventory; + public event AgentSit OnUndo; + public event AgentSit OnRedo; + public event LandUndo OnLandUndo; + + public event ForceReleaseControls OnForceReleaseControls; + + public event GodLandStatRequest OnLandStatRequest; + public event RequestObjectPropertiesFamily OnObjectGroupRequest; + + public event DetailedEstateDataRequest OnDetailedEstateDataRequest; + public event SetEstateFlagsRequest OnSetEstateFlagsRequest; + public event SetEstateTerrainBaseTexture OnSetEstateTerrainBaseTexture; + public event SetEstateTerrainDetailTexture OnSetEstateTerrainDetailTexture; + public event SetEstateTerrainTextureHeights OnSetEstateTerrainTextureHeights; + public event CommitEstateTerrainTextureRequest OnCommitEstateTerrainTextureRequest; + public event SetRegionTerrainSettings OnSetRegionTerrainSettings; + public event EstateRestartSimRequest OnEstateRestartSimRequest; + public event EstateChangeCovenantRequest OnEstateChangeCovenantRequest; + public event UpdateEstateAccessDeltaRequest OnUpdateEstateAccessDeltaRequest; + public event SimulatorBlueBoxMessageRequest OnSimulatorBlueBoxMessageRequest; + public event EstateBlueBoxMessageRequest OnEstateBlueBoxMessageRequest; + public event EstateDebugRegionRequest OnEstateDebugRegionRequest; + public event EstateTeleportOneUserHomeRequest OnEstateTeleportOneUserHomeRequest; + public event EstateTeleportAllUsersHomeRequest OnEstateTeleportAllUsersHomeRequest; + public event ScriptReset OnScriptReset; + public event GetScriptRunning OnGetScriptRunning; + public event SetScriptRunning OnSetScriptRunning; + public event Action OnAutoPilotGo; + + public event TerrainUnacked OnUnackedTerrain; + + public event RegionHandleRequest OnRegionHandleRequest; + public event ParcelInfoRequest OnParcelInfoRequest; + + public event ActivateGesture OnActivateGesture; + public event DeactivateGesture OnDeactivateGesture; + public event ObjectOwner OnObjectOwner; + + public event DirPlacesQuery OnDirPlacesQuery; + public event DirFindQuery OnDirFindQuery; + public event DirLandQuery OnDirLandQuery; + public event DirPopularQuery OnDirPopularQuery; + public event DirClassifiedQuery OnDirClassifiedQuery; + public event EventInfoRequest OnEventInfoRequest; + public event ParcelSetOtherCleanTime OnParcelSetOtherCleanTime; + + public event MapItemRequest OnMapItemRequest; + + public event OfferCallingCard OnOfferCallingCard; + public event AcceptCallingCard OnAcceptCallingCard; + public event DeclineCallingCard OnDeclineCallingCard; + + public event SoundTrigger OnSoundTrigger; + + public event StartLure OnStartLure; + public event TeleportLureRequest OnTeleportLureRequest; + public event NetworkStats OnNetworkStatsUpdate; + + public event ClassifiedInfoRequest OnClassifiedInfoRequest; + public event ClassifiedInfoUpdate OnClassifiedInfoUpdate; + public event ClassifiedDelete OnClassifiedDelete; + public event ClassifiedGodDelete OnClassifiedGodDelete; + + public event EventNotificationAddRequest OnEventNotificationAddRequest; + public event EventNotificationRemoveRequest OnEventNotificationRemoveRequest; + public event EventGodDelete OnEventGodDelete; + + public event ParcelDwellRequest OnParcelDwellRequest; + + public event UserInfoRequest OnUserInfoRequest; + public event UpdateUserInfo OnUpdateUserInfo; + + public event RetrieveInstantMessages OnRetrieveInstantMessages; + + public event PickDelete OnPickDelete; + public event PickGodDelete OnPickGodDelete; + public event PickInfoUpdate OnPickInfoUpdate; + public event AvatarNotesUpdate OnAvatarNotesUpdate; + + public event MuteListRequest OnMuteListRequest; + + public event AvatarInterestUpdate OnAvatarInterestUpdate; + + public event PlacesQuery OnPlacesQuery; + + public event FindAgentUpdate OnFindAgent; + public event TrackAgentUpdate OnTrackAgent; + public event NewUserReport OnUserReport; + public event SaveStateHandler OnSaveState; + public event GroupAccountSummaryRequest OnGroupAccountSummaryRequest; + public event GroupAccountDetailsRequest OnGroupAccountDetailsRequest; + public event GroupAccountTransactionsRequest OnGroupAccountTransactionsRequest; + public event FreezeUserUpdate OnParcelFreezeUser; + public event EjectUserUpdate OnParcelEjectUser; + public event ParcelBuyPass OnParcelBuyPass; + public event ParcelGodMark OnParcelGodMark; + public event GroupActiveProposalsRequest OnGroupActiveProposalsRequest; + public event GroupVoteHistoryRequest OnGroupVoteHistoryRequest; + public event SimWideDeletesDelegate OnSimWideDeletes; + public event SendPostcard OnSendPostcard; + public event ChangeInventoryItemFlags OnChangeInventoryItemFlags; + public event MuteListEntryUpdate OnUpdateMuteListEntry; + public event MuteListEntryRemove OnRemoveMuteListEntry; + public event GodlikeMessage onGodlikeMessage; + public event GodUpdateRegionInfoUpdate OnGodUpdateRegionInfoUpdate; + public event GenericCall2 OnUpdateThrottles; + public event AgentFOV OnAgentFOV; + +#pragma warning restore 67 + + /// + /// This agent's UUID + /// + private UUID m_agentId; + + public ISceneAgent SceneAgent { get; set; } + + public bool SupportObjectAnimations { get; set; } + + /// + /// The last caps seed url that this client was given. + /// + public string CapsSeedUrl; + + private Vector3 startPos = new Vector3(((int)Constants.RegionSize * 0.5f), ((int)Constants.RegionSize * 0.5f), 2); + + public virtual Vector3 StartPos + { + get { return startPos; } + set { } + } + + public float StartFar { get; set; } + + public virtual UUID AgentId + { + get { return m_agentId; } + } + + public UUID SessionId { get; set; } + + public UUID SecureSessionId { get; set; } + + public virtual string FirstName + { + get { return m_firstName; } + } + private string m_firstName; + + public virtual string LastName + { + get { return m_lastName; } + } + private string m_lastName; + + public virtual String Name + { + get { return FirstName + " " + LastName; } + } + + public int PingTimeMS { get { return 0; } } + + public bool IsActive + { + get { return true; } + set { } + } + + public bool IsLoggingOut { get; set; } + + public UUID ActiveGroupId + { + get { return UUID.Zero; } + set { } + } + + public string ActiveGroupName + { + get { return String.Empty; } + set { } + } + + public ulong ActiveGroupPowers + { + get { return 0; } + set { } + } + + public bool IsGroupMember(UUID groupID) + { + return false; + } + + public Dictionary GetGroupPowers() + { + return new Dictionary(); + } + + public void SetGroupPowers(Dictionary powers) { } + + public ulong GetGroupPowers(UUID groupID) + { + return 0; + } + + public virtual int NextAnimationSequenceNumber + { + get { return 1; } + set { } + } + + public IScene Scene + { + get { return m_scene; } + } + + public UUID ScopeId + { + get { return UUID.Zero; } + } + + public bool SendLogoutPacketWhenClosing + { + set { } + } + + private uint m_circuitCode; + + public uint CircuitCode + { + get { return m_circuitCode; } + set { m_circuitCode = value; } + } + + public IPEndPoint RemoteEndPoint + { + get { return new IPEndPoint(IPAddress.Loopback, (ushort)m_circuitCode); } + } + + public List SelectedObjects {get; private set;} + + /// + /// Constructor + /// + /// + /// + /// + public TestClient(AgentCircuitData agentData, Scene scene) + { + m_agentId = agentData.AgentID; + m_firstName = agentData.firstname; + m_lastName = agentData.lastname; + m_circuitCode = agentData.circuitcode; + m_scene = scene; + SessionId = agentData.SessionID; + SecureSessionId = agentData.SecureSessionID; + CapsSeedUrl = agentData.CapsPath; + + ReceivedKills = new List(); + ReceivedOfflineNotifications = new List(); + ReceivedOnlineNotifications = new List(); + ReceivedFriendshipTerminations = new List(); + + SentImageDataPackets = new List(); + SentImagePacketPackets = new List(); + SentImageNotInDatabasePackets = new List(); + } + + /// + /// Trigger chat coming from this connection. + /// + /// + /// + /// + public bool Chat(int channel, ChatTypeEnum type, string message) + { + ChatMessage handlerChatFromClient = OnChatFromClient; + + if (handlerChatFromClient != null) + { + OSChatMessage args = new OSChatMessage(); + args.Channel = channel; + args.From = Name; + args.Message = message; + args.Type = type; + + args.Scene = Scene; + args.Sender = this; + args.SenderUUID = AgentId; + + handlerChatFromClient(this, args); + } + + return true; + } + + /// + /// Attempt a teleport to the given region. + /// + /// + /// + /// + public void Teleport(ulong regionHandle, Vector3 position, Vector3 lookAt) + { + OnTeleportLocationRequest(this, regionHandle, position, lookAt, 16); + } + + public void CompleteMovement() + { + if (OnCompleteMovementToRegion != null) + OnCompleteMovementToRegion(this, true); + } + + /// + /// Emulate sending an IM from the viewer to the simulator. + /// + /// + public void HandleImprovedInstantMessage(GridInstantMessage im) + { + ImprovedInstantMessage handlerInstantMessage = OnInstantMessage; + + if (handlerInstantMessage != null) + handlerInstantMessage(this, im); + } + + public virtual void ActivateGesture(UUID assetId, UUID gestureId) + { + } + + public virtual void SendWearables(AvatarWearable[] wearables, int serial) + { + } + + public virtual void SendAppearance(UUID agentID, byte[] visualParams, byte[] textureEntry, float hover) + { + } + + public void SendCachedTextureResponse(ISceneEntity avatar, int serial, List cachedTextures) + { + + } + + public virtual void Kick(string message) + { + } + + public virtual void SendAvatarPickerReply(UUID QueryID, List users) + { + } + + public virtual void SendAgentDataUpdate(UUID agentid, UUID activegroupid, string firstname, string lastname, ulong grouppowers, string groupname, string grouptitle) + { + } + + public virtual void SendKillObject(List localID) + { + ReceivedKills.AddRange(localID); + } + + public void SendPartFullUpdate(ISceneEntity ent, uint? parentID) + { + } + + public virtual void SetChildAgentThrottle(byte[] throttle) + { + } + + public virtual void SetChildAgentThrottle(byte[] throttle, float factor) + { + } + + public void SetAgentThrottleSilent(int throttle, int setting) + { + } + + public int GetAgentThrottleSilent(int throttle) + { + return 0; + } + + public byte[] GetThrottlesPacked(float multiplier) + { + return Array.Empty(); + } + + public virtual void SendAnimations(UUID[] animations, int[] seqs, UUID sourceAgentId, UUID[] objectIDs) + { + } + + public virtual void SendChatMessage( + string message, byte type, Vector3 fromPos, string fromName, + UUID fromAgentID, UUID ownerID, byte source, byte audible) + { +// Console.WriteLine("mmm {0} {1} {2}", message, Name, AgentId); + if (OnReceivedChatMessage != null) + OnReceivedChatMessage(message, type, fromPos, fromName, fromAgentID, ownerID, source, audible); + } + + public void SendInstantMessage(GridInstantMessage im) + { + if (OnReceivedInstantMessage != null) + OnReceivedInstantMessage(im); + } + + public void SendGenericMessage(string method, UUID invoice, List message) + { + + } + + public void SendGenericMessage(string method, UUID invoice, List message) + { + + } + + public virtual bool CanSendLayerData() + { + return false; + } + + public virtual void SendLayerData() + { + } + + public void SendLayerData(int[] map) + { + } + + public virtual void SendWindData(int version, Vector2[] windSpeeds) { } + + public virtual void MoveAgentIntoRegion(RegionInfo regInfo, Vector3 pos, Vector3 look) + { + if (OnReceivedMoveAgentIntoRegion != null) + OnReceivedMoveAgentIntoRegion(regInfo, pos, look); + } + + public virtual AgentCircuitData RequestClientInfo() + { + AgentCircuitData agentData = new AgentCircuitData(); + agentData.AgentID = AgentId; + agentData.SessionID = SessionId; + agentData.SecureSessionID = UUID.Zero; + agentData.circuitcode = m_circuitCode; + agentData.child = false; + agentData.firstname = m_firstName; + agentData.lastname = m_lastName; + + ICapabilitiesModule capsModule = m_scene.RequestModuleInterface(); + if (capsModule != null) + { + agentData.CapsPath = capsModule.GetCapsPath(m_agentId); + agentData.ChildrenCapSeeds = new Dictionary(capsModule.GetChildrenSeeds(m_agentId)); + } + + return agentData; + } + + public virtual void InformClientOfNeighbour(ulong neighbourHandle, IPEndPoint neighbourExternalEndPoint) + { + if (OnTestClientInformClientOfNeighbour != null) + OnTestClientInformClientOfNeighbour(neighbourHandle, neighbourExternalEndPoint); + } + + public virtual void SendRegionTeleport( + ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, + uint locationID, uint flags, string capsURL) + { + CapsSeedUrl = capsURL; + + if (OnTestClientSendRegionTeleport != null) + OnTestClientSendRegionTeleport( + regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL); + } + + public virtual void SendTeleportFailed(string reason) + { + } + + public virtual void CrossRegion(ulong newRegionHandle, Vector3 pos, Vector3 lookAt, + IPEndPoint newRegionExternalEndPoint, string capsURL) + { + // This is supposed to send a packet to the client telling it's ready to start region crossing. + // Instead I will just signal I'm ready, mimicking the communication behavior. + // It's ugly, but avoids needless communication setup. This is used in ScenePresenceTests.cs. + // Arthur V. + + wh.Set(); + } + + public virtual void SendMapBlock(List mapBlocks, uint flag) + { + } + + public virtual void SendLocalTeleport(Vector3 position, Vector3 lookAt, uint flags) + { + } + + public virtual void SendTeleportStart(uint flags) + { + } + + public void SendTeleportProgress(uint flags, string message) + { + } + + public virtual void SendMoneyBalance(UUID transaction, bool success, byte[] description, int balance, int transactionType, UUID sourceID, bool sourceIsGroup, UUID destID, bool destIsGroup, int amount, string item) + { + } + + public virtual void SendPayPrice(UUID objectID, int[] payPrice) + { + } + + public virtual void SendCoarseLocationUpdate(List users, List CoarseLocations) + { + } + + public virtual void SendDialog(string objectname, UUID objectID, UUID ownerID, string ownerFirstName, string ownerLastName, string msg, UUID textureID, int ch, string[] buttonlabels) + { + } + + public void SendEntityFullUpdateImmediate(ISceneEntity ent) + { + } + + public void SendEntityTerseUpdateImmediate(ISceneEntity ent) + { + } + + public void SendEntityUpdate(ISceneEntity entity, PrimUpdateFlags updateFlags) + { + if (OnReceivedEntityUpdate != null) + OnReceivedEntityUpdate(entity, updateFlags); + } + + public void ReprioritizeUpdates() + { + } + + public void FlushPrimUpdates() + { + } + + public void SendInventoryFolderDetails(UUID ownerID, UUID folderID, + List items, + List folders, + int version, + int descendents, + bool fetchFolders, + bool fetchItems) + { + } + + public void SendInventoryItemDetails(InventoryItemBase[] items) + { + } + + public void SendInventoryItemCreateUpdate(InventoryItemBase Item, uint callbackID) + { + } + + public void SendInventoryItemCreateUpdate(InventoryItemBase Item, UUID transactionID, uint callbackId) + { + } + + public void SendRemoveInventoryItem(UUID itemID) + { + } + + public void SendRemoveInventoryItems(UUID[] items) + { + } + + public void SendBulkUpdateInventory(InventoryNodeBase node, UUID? transactionID = null) + { + } + + public void SendBulkUpdateInventory(InventoryFolderBase[] folders, InventoryItemBase[] items) + { + } + + public void SendTakeControls(int controls, bool passToAgent, bool TakeControls) + { + } + + public virtual void SendTaskInventory(UUID taskID, short serial, byte[] fileName) + { + } + + public virtual void SendXferPacket(ulong xferID, uint packet, + byte[] XferData, int XferDataOffset, int XferDatapktLen, bool isTaskInventory) + { + } + + public virtual void SendAbortXferPacket(ulong xferID) + { + + } + + public virtual void SendEconomyData(float EnergyEfficiency, int ObjectCapacity, int ObjectCount, int PriceEnergyUnit, + int PriceGroupCreate, int PriceObjectClaim, float PriceObjectRent, float PriceObjectScaleFactor, + int PriceParcelClaim, float PriceParcelClaimFactor, int PriceParcelRent, int PricePublicObjectDecay, + int PricePublicObjectDelete, int PriceRentLight, int PriceUpload, int TeleportMinPrice, float TeleportPriceExponent) + { + } + + public virtual void SendNameReply(UUID profileId, string firstname, string lastname) + { + } + + public virtual void SendPreLoadSound(UUID objectID, UUID ownerID, UUID soundID) + { + } + + public virtual void SendPlayAttachedSound(UUID soundID, UUID objectID, UUID ownerID, float gain, + byte flags) + { + } + + public void SendTriggeredSound(UUID soundID, UUID ownerID, UUID objectID, UUID parentID, ulong handle, Vector3 position, float gain) + { + } + + public void SendAttachedSoundGainChange(UUID objectID, float gain) + { + + } + + public void SendAlertMessage(string message) + { + } + + public void SendAgentAlertMessage(string message, bool modal) + { + } + + public void SendAlertMessage(string message, string info) + { + } + + public void SendSystemAlertMessage(string message) + { + } + + public void SendLoadURL(string objectname, UUID objectID, UUID ownerID, bool groupOwned, string message, + string url) + { + } + + public virtual void SendRegionHandshake() + { + if (OnRegionHandShakeReply != null) + { + OnRegionHandShakeReply(this); + } + } + + public void SendAssetUploadCompleteMessage(sbyte AssetType, bool Success, UUID AssetFullID) + { + } + + public void SendConfirmXfer(ulong xferID, uint PacketID) + { + } + + public void SendXferRequest(ulong XferID, short AssetType, UUID vFileID, byte FilePath, byte[] FileName) + { + } + + public void SendInitiateDownload(string simFileName, string clientFileName) + { + } + + public void SendImageFirstPart(ushort numParts, UUID ImageUUID, uint ImageSize, byte[] ImageData, byte imageCodec) + { + ImageDataPacket im = new ImageDataPacket(); + im.Header.Reliable = false; + im.ImageID.Packets = numParts; + im.ImageID.ID = ImageUUID; + + if (ImageSize > 0) + im.ImageID.Size = ImageSize; + + im.ImageData.Data = ImageData; + im.ImageID.Codec = imageCodec; + im.Header.Zerocoded = true; + SentImageDataPackets.Add(im); + } + + public void SendImageNextPart(ushort partNumber, UUID imageUuid, byte[] imageData) + { + ImagePacketPacket im = new ImagePacketPacket(); + im.Header.Reliable = false; + im.ImageID.Packet = partNumber; + im.ImageID.ID = imageUuid; + im.ImageData.Data = imageData; + SentImagePacketPackets.Add(im); + } + + public void SendImageNotFound(UUID imageid) + { + ImageNotInDatabasePacket p = new ImageNotInDatabasePacket(); + p.ImageID.ID = imageid; + + SentImageNotInDatabasePackets.Add(p); + } + + public void SendShutdownConnectionNotice() + { + } + + public void SendSimStats(SimStats stats) + { + } + + public void SendObjectPropertiesFamilyData(ISceneEntity Entity, uint RequestFlags) + { + } + + public void SendObjectPropertiesReply(ISceneEntity entity) + { + } + + public void SendAgentOffline(UUID[] agentIDs) + { + ReceivedOfflineNotifications.AddRange(agentIDs); + } + + public void SendAgentOnline(UUID[] agentIDs) + { + ReceivedOnlineNotifications.AddRange(agentIDs); + } + + public void SendFindAgent(UUID HunterID, UUID PreyID, double GlobalX, double GlobalY) + { + } + + public void SendSitResponse(UUID TargetID, Vector3 OffsetPos, + Quaternion SitOrientation, bool autopilot, + Vector3 CameraAtOffset, Vector3 CameraEyeOffset, bool ForceMouseLook) + { + } + + public void SendAdminResponse(UUID Token, uint AdminLevel) + { + } + + public void SendGroupMembership(GroupMembershipData[] GroupMembership) + { + } + + public void SendViewerTime(Vector3 sunDir, float sunphase) + { + } + + public void SendViewerEffect(ViewerEffectPacket.EffectBlock[] effectBlocks) + { + } + + public void SendAvatarProperties(UUID avatarID, string aboutText, string bornOn, Byte[] membershipType, + string flAbout, uint flags, UUID flImageID, UUID imageID, string profileURL, + UUID partnerID) + { + } + + public int DebugPacketLevel { get; set; } + public float FOV { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public int viewHeight { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + public int viewWidth { get => throw new NotImplementedException(); set => throw new NotImplementedException(); } + + public ViewerFlags ViewerFlags => throw new NotImplementedException(); + + public void InPacket(object NewPack) + { + } + + public void ProcessInPacket(Packet NewPack) + { + } + + /// + /// This is a TestClient only method to do shutdown tasks that are normally carried out by LLUDPServer.RemoveClient() + /// + public void Logout() + { + // We must set this here so that the presence is removed from the PresenceService by the PresenceDetector + IsLoggingOut = true; + + Close(); + } + + public void Close() + { + Close(true, false); + } + + public void Close(bool sendStop, bool force) + { + // Fire the callback for this connection closing + // This is necesary to get the presence detector to notice that a client has logged out. + if (OnConnectionClosed != null) + OnConnectionClosed(this); + + m_scene.RemoveClient(AgentId, true); + } + + public void Start() + { + throw new NotImplementedException(); + } + + public void Stop() + { + } + + public void SendBlueBoxMessage(UUID FromAvatarID, String FromAvatarName, String Message) + { + + } + public void SendLogoutPacket() + { + } + + public void Terminate() + { + } + + public ClientInfo GetClientInfo() + { + return null; + } + + public void SetClientInfo(ClientInfo info) + { + } + + public void SendScriptQuestion(UUID objectID, string taskName, string ownerName, UUID itemID, int question) + { + } + public void SendHealth(float health) + { + } + + public void SendTelehubInfo(UUID ObjectID, string ObjectName, Vector3 ObjectPos, Quaternion ObjectRot, List SpawnPoint) + { + } + + public void SendEstateList(UUID invoice, int code, UUID[] Data, uint estateID) + { + } + + public void SendBannedUserList(UUID invoice, EstateBan[] banlist, uint estateID) + { + } + + public void SendRegionInfoToEstateMenu(RegionInfoForEstateMenuArgs args) + { + } + + public void SendEstateCovenantInformation(UUID covenant) + { + } + + public void SendDetailedEstateData(UUID invoice, string estateName, uint estateID, uint parentEstate, uint estateFlags, uint sunPosition, UUID covenant, uint covenantChanged, string abuseEmail, UUID estateOwner) + { + } + + public void SendLandProperties(int sequence_id, bool snap_selection, int request_result, ILandObject lo, float simObjectBonusFactor, int parcelObjectCapacity, int simObjectCapacity, uint regionFlags) + { + } + + public void SendLandAccessListData(List accessList, uint accessFlag, int localLandID) + { + } + + public void SendForceClientSelectObjects(List objectIDs) + { + } + + public void SendCameraConstraint(Vector4 ConstraintPlane) + { + } + + public void SendLandObjectOwners(LandData land, List groups, Dictionary ownersAndCount) + { + } + + public void SendLandParcelOverlay(byte[] data, int sequence_id) + { + } + + public void SendParcelMediaCommand(uint flags, ParcelMediaCommandEnum command, float time) + { + } + + public void SendParcelMediaUpdate(string mediaUrl, UUID mediaTextureID, byte autoScale, string mediaType, + string mediaDesc, int mediaWidth, int mediaHeight, byte mediaLoop) + { + } + + public void SendGroupNameReply(UUID groupLLUID, string GroupName) + { + } + + public void SendLandStatReply(uint reportType, uint requestFlags, uint resultCount, LandStatReportItem[] lsrpia) + { + } + + public void SendScriptRunningReply(UUID objectID, UUID itemID, bool running) + { + } + + public void SendAsset(AssetRequestToClient req) + { + } + + public void SendTexture(AssetBase TextureAsset) + { + + } + + public void SendSetFollowCamProperties (UUID objectID, SortedDictionary parameters) + { + } + + public void SendClearFollowCamProperties (UUID objectID) + { + } + + public void SendRegionHandle (UUID regoinID, ulong handle) + { + } + + public void SendParcelInfo (RegionInfo info, LandData land, UUID parcelID, uint x, uint y) + { + } + + public void SetClientOption(string option, string value) + { + } + + public string GetClientOption(string option) + { + return string.Empty; + } + + public void SendScriptTeleportRequest(string objName, string simName, Vector3 pos, Vector3 lookAt) + { + } + + public void SendDirPlacesReply(UUID queryID, DirPlacesReplyData[] data) + { + } + + public void SendDirPeopleReply(UUID queryID, DirPeopleReplyData[] data) + { + } + + public void SendDirEventsReply(UUID queryID, DirEventsReplyData[] data) + { + } + + public void SendDirGroupsReply(UUID queryID, DirGroupsReplyData[] data) + { + } + + public void SendDirClassifiedReply(UUID queryID, DirClassifiedReplyData[] data) + { + } + + public void SendDirLandReply(UUID queryID, DirLandReplyData[] data) + { + } + + public void SendDirPopularReply(UUID queryID, DirPopularReplyData[] data) + { + } + + public void SendMapItemReply(mapItemReply[] replies, uint mapitemtype, uint flags) + { + } + + public void SendEventInfoReply (EventData info) + { + } + + public void SendOfferCallingCard (UUID destID, UUID transactionID) + { + } + + public void SendAcceptCallingCard (UUID transactionID) + { + } + + public void SendDeclineCallingCard (UUID transactionID) + { + } + + public void SendAvatarGroupsReply(UUID avatarID, GroupMembershipData[] data) + { + } + + public void SendAgentGroupDataUpdate(UUID avatarID, GroupMembershipData[] data) + { + } + + public void SendJoinGroupReply(UUID groupID, bool success) + { + } + + public void SendEjectGroupMemberReply(UUID agentID, UUID groupID, bool succss) + { + } + + public void SendLeaveGroupReply(UUID groupID, bool success) + { + } + + public void SendTerminateFriend(UUID exFriendID) + { + ReceivedFriendshipTerminations.Add(exFriendID); + } + + public bool AddGenericPacketHandler(string MethodName, GenericMessage handler) + { + //throw new NotImplementedException(); + return false; + } + + public void SendAvatarClassifiedReply(UUID targetID, UUID[] classifiedID, string[] name) + { + } + + public void SendClassifiedInfoReply(UUID classifiedID, UUID creatorID, uint creationDate, uint expirationDate, uint category, string name, string description, UUID parcelID, uint parentEstate, UUID snapshotID, string simName, Vector3 globalPos, string parcelName, byte classifiedFlags, int price) + { + } + + public void SendAgentDropGroup(UUID groupID) + { + } + + public void SendAvatarNotesReply(UUID targetID, string text) + { + } + + public void SendAvatarPicksReply(UUID targetID, Dictionary picks) + { + } + + public void SendAvatarClassifiedReply(UUID targetID, Dictionary classifieds) + { + } + + public void SendParcelDwellReply(int localID, UUID parcelID, float dwell) + { + } + + public void SendUserInfoReply(bool imViaEmail, bool visible, string email) + { + } + + public void SendCreateGroupReply(UUID groupID, bool success, string message) + { + } + + public void RefreshGroupMembership() + { + } + + public void UpdateGroupMembership(GroupMembershipData[] data) + { + } + + public void GroupMembershipRemove(UUID GroupID) + { + } + + public void GroupMembershipAddReplace(UUID GroupID,ulong GroupPowers) + { + } + + public void SendUseCachedMuteList() + { + } + + public void SendEmpytMuteList() + { + } + + public void SendMuteListUpdate(string filename) + { + } + + public void SendPickInfoReply(UUID pickID,UUID creatorID, bool topPick, UUID parcelID, string name, string desc, UUID snapshotID, string user, string originalName, string simName, Vector3 posGlobal, int sortOrder, bool enabled) + { + } + + public bool TryGet(out T iface) + { + iface = default(T); + return false; + } + + public T Get() + { + return default(T); + } + + public void Disconnect(string reason) + { + } + + public void Disconnect() + { + } + + public void SendRebakeAvatarTextures(UUID textureID) + { + if (OnReceivedSendRebakeAvatarTextures != null) + OnReceivedSendRebakeAvatarTextures(textureID); + } + + public void SendAvatarInterestsReply(UUID avatarID, uint wantMask, string wantText, uint skillsMask, string skillsText, string languages) + { + } + + public void SendGroupAccountingDetails(IClientAPI sender,UUID groupID, UUID transactionID, UUID sessionID, int amt) + { + } + + public void SendGroupAccountingSummary(IClientAPI sender,UUID groupID, uint moneyAmt, int totalTier, int usedTier) + { + } + + public void SendGroupTransactionsSummaryDetails(IClientAPI sender,UUID groupID, UUID transactionID, UUID sessionID,int amt) + { + } + + public void SendGroupVoteHistory(UUID groupID, UUID transactionID, GroupVoteHistory[] Votes) + { + } + + public void SendGroupActiveProposals(UUID groupID, UUID transactionID, GroupActiveProposals[] Proposals) + { + } + + public void SendChangeUserRights(UUID agentID, UUID friendID, int rights) + { + } + + public void SendTextBoxRequest(string message, int chatChannel, string objectname, UUID ownerID, string ownerFirstName, string ownerLastName, UUID objectId) + { + } + + public void SendAgentTerseUpdate(ISceneEntity presence) + { + } + + public void SendPlacesReply(UUID queryID, UUID transactionID, PlacesReplyData[] data) + { + } + + public void SendSelectedPartsProprieties(List parts) + { + } + + public void SendPartPhysicsProprieties(ISceneEntity entity) + { + } + + public uint GetViewerCaps() + { + return 0x1000; + } + + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/TestEventQueueGetModule.cs b/Tests/OpenSim.Tests.Common/Mock/TestEventQueueGetModule.cs new file mode 100644 index 00000000000..aa81cf9d2ba --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestEventQueueGetModule.cs @@ -0,0 +1,228 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Net; +using Nini.Config; +using OpenMetaverse; +using OpenMetaverse.StructuredData; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Tests.Common +{ + public class TestEventQueueGetModule : IEventQueue, INonSharedRegionModule + { + public class Event + { + public string Name { get; set; } + public object[] Args { get; set; } + + public Event(string name, object[] args) + { + name = Name; + args = Args; + } + } + + public Dictionary> Events { get; set; } + + public void Initialise(IConfigSource source) {} + + public void Close() {} + + public void AddRegion(Scene scene) + { + Events = new Dictionary>(); + scene.RegisterModuleInterface(this); + } + + public void RemoveRegion (Scene scene) {} + + public void RegionLoaded (Scene scene) {} + + public string Name { get { return "TestEventQueueGetModule"; } } + + public Type ReplaceableInterface { get { return null; } } + + private void AddEvent(UUID avatarID, string name, params object[] args) + { + Console.WriteLine("Adding event {0} for {1}", name, avatarID); + + List avEvents; + + if (!Events.ContainsKey(avatarID)) + { + avEvents = new List(); + Events[avatarID] = avEvents; + } + else + { + avEvents = Events[avatarID]; + } + + avEvents.Add(new Event(name, args)); + } + + public void ClearEvents() + { + if (Events != null) + Events.Clear(); + } + + public bool Enqueue(string o, UUID avatarID) + { + AddEvent(avatarID, "Enqueue", o); + return true; + } + public bool Enqueue(byte[] o, UUID avatarID) + { + return true; + } + public bool Enqueue(OSD o, UUID avatarID) + { + return true; + } + public bool Enqueue(osUTF8 o, UUID avatarID) + { + return true; + } + public void EnableSimulator (ulong handle, IPEndPoint endPoint, UUID avatarID, int regionSizeX, int regionSizeY) + { + AddEvent(avatarID, "EnableSimulator", handle); + } + + public void EstablishAgentCommunication (UUID avatarID, IPEndPoint endPoint, string capsPath, + ulong regionHandle, int regionSizeX, int regionSizeY) + { + AddEvent(avatarID, "EstablishAgentCommunication", endPoint, capsPath); + } + + public void TeleportFinishEvent (ulong regionHandle, byte simAccess, IPEndPoint regionExternalEndPoint, + uint locationID, uint flags, string capsURL, UUID agentID, int regionSizeX, int regionSizeY) + { + AddEvent(agentID, "TeleportFinishEvent", regionHandle, simAccess, regionExternalEndPoint, locationID, flags, capsURL); + } + + public void CrossRegion (ulong handle, Vector3 pos, Vector3 lookAt, IPEndPoint newRegionExternalEndPoint, + string capsURL, UUID avatarID, UUID sessionID, int regionSizeX, int regionSizeY) + { + AddEvent(avatarID, "CrossRegion", handle, pos, lookAt, newRegionExternalEndPoint, capsURL, sessionID); + } + + public void ChatterboxInvitation( + UUID sessionID, string sessionName, UUID fromAgent, string message, UUID toAgent, string fromName, + byte dialog, uint timeStamp, bool offline, int parentEstateID, Vector3 position, uint ttl, + UUID transactionID, bool fromGroup, byte[] binaryBucket) + { + AddEvent( + toAgent, "ChatterboxInvitation", sessionID, sessionName, fromAgent, message, toAgent, fromName, dialog, + timeStamp, offline, parentEstateID, position, ttl, transactionID, fromGroup, binaryBucket); + } + + public void ChatterBoxSessionStartReply(UUID sessionID, string sessionName, int type, + bool voiceEnabled, bool voiceModerated, UUID tmpSessionID, + bool sucess, string error, + UUID toAgent) + { + AddEvent(toAgent, "ChatterBoxSessionStartReply", sessionID, sessionName, type, + voiceEnabled, voiceModerated, tmpSessionID, + sucess, error); + } + + public void ChatterBoxSessionAgentListUpdates (UUID sessionID, UUID toAgent, List updates) + { + AddEvent(toAgent, "ChatterBoxSessionAgentListUpdates", sessionID, toAgent, updates); + } + + public void ChatterBoxForceClose (UUID toAgent, UUID sessionID, string reason) + { + AddEvent(toAgent, "ForceCloseChatterBoxSession", sessionID, reason); + } + + public void ParcelProperties (OpenMetaverse.Messages.Linden.ParcelPropertiesMessage parcelPropertiesMessage, UUID avatarID) + { + AddEvent(avatarID, "ParcelProperties", parcelPropertiesMessage); + } + + public void GroupMembershipData(UUID receiverAgent, GroupMembershipData[] data) + { + AddEvent(receiverAgent, "AgentGroupDataUpdate", data); + } + + public void ScriptRunningEvent (UUID objectID, UUID itemID, bool running, UUID avatarID) + { + Console.WriteLine("ONE"); + throw new System.NotImplementedException (); + } + + public byte[] BuildEvent(string eventName, OSD eventBody) + { + Console.WriteLine("TWOoo"); + throw new System.NotImplementedException(); + } + + public void partPhysicsProperties (uint localID, byte physhapetype, float density, float friction, float bounce, float gravmod, UUID avatarID) + { + AddEvent(avatarID, "partPhysicsProperties", localID, physhapetype, density, friction, bounce, gravmod); + } + + public void WindlightRefreshEvent(int interpolate, UUID avatarID) + { + } + + public void SendBulkUpdateInventoryItem(InventoryItemBase item, UUID avatarID, UUID? transationID = null) + { + } + + public osUTF8 StartEvent(string eventName) + { + return null; + } + + public osUTF8 StartEvent(string eventName, int cap) + { + return null; + } + + public byte[] EndEventToBytes(osUTF8 sb) + { + return Array.Empty(); + } + + public void SendLargeGenericMessage(UUID avatarID, UUID? transationID, UUID? sessionID, string method, UUID invoice, List message) + { + throw new NotImplementedException(); + } + + public void SendLargeGenericMessage(UUID avatarID, UUID? transationID, UUID? sessionID, string method, UUID invoice, List message) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestGroupsDataPlugin.cs b/Tests/OpenSim.Tests.Common/Mock/TestGroupsDataPlugin.cs new file mode 100644 index 00000000000..8e2d8e6c989 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestGroupsDataPlugin.cs @@ -0,0 +1,339 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +using OpenMetaverse; +using OpenSim.Data; + +namespace OpenSim.Tests.Common.Mock +{ + public class TestGroupsDataPlugin : IGroupsData + { + class CompositeKey + { + private readonly string _key; + public string Key + { + get { return _key; } + } + + public CompositeKey(UUID _k1, string _k2) + { + _key = _k1.ToString() + _k2; + } + + public CompositeKey(UUID _k1, string _k2, string _k3) + { + _key = _k1.ToString() + _k2 + _k3; + } + + public override bool Equals(object obj) + { + if (obj is CompositeKey) + { + return Key == ((CompositeKey)obj).Key; + } + return false; + } + + public override int GetHashCode() + { + return base.GetHashCode(); + } + + public override string ToString() + { + return Key; + } + } + + private Dictionary m_Groups; + private Dictionary m_Membership; + private Dictionary m_Roles; + private Dictionary m_RoleMembership; + private Dictionary m_Invites; + private Dictionary m_Notices; + private Dictionary m_Principals; + + public TestGroupsDataPlugin(string connectionString, string realm) + { + m_Groups = new Dictionary(); + m_Membership = new Dictionary(); + m_Roles = new Dictionary(); + m_RoleMembership = new Dictionary(); + m_Invites = new Dictionary(); + m_Notices = new Dictionary(); + m_Principals = new Dictionary(); + } + + #region groups table + public bool StoreGroup(GroupData data) + { + return false; + } + + public GroupData RetrieveGroup(UUID groupID) + { + if (m_Groups.ContainsKey(groupID)) + return m_Groups[groupID]; + + return null; + } + + public GroupData RetrieveGroup(string name) + { + return m_Groups.Values.First(g => g.Data.ContainsKey("Name") && g.Data["Name"] == name); + } + + public GroupData[] RetrieveGroups(string pattern) + { + if (string.IsNullOrEmpty(pattern)) + pattern = "1"; + + IEnumerable groups = m_Groups.Values.Where(g => g.Data.ContainsKey("Name") && (g.Data["Name"].StartsWith(pattern) || g.Data["Name"].EndsWith(pattern))); + + return (groups != null) ? groups.ToArray() : new GroupData[0]; + } + + public bool DeleteGroup(UUID groupID) + { + return m_Groups.Remove(groupID); + } + + public int GroupsCount() + { + return m_Groups.Count; + } + #endregion + + #region membership table + public MembershipData RetrieveMember(UUID groupID, string pricipalID) + { + CompositeKey dkey = new CompositeKey(groupID, pricipalID); + if (m_Membership.ContainsKey(dkey)) + return m_Membership[dkey]; + + return null; + } + + public MembershipData[] RetrieveMembers(UUID groupID) + { + IEnumerable keys = m_Membership.Keys.Where(k => k.Key.StartsWith(groupID.ToString())); + return keys.Where(m_Membership.ContainsKey).Select(x => m_Membership[x]).ToArray(); + } + + public MembershipData[] RetrieveMemberships(string principalID) + { + IEnumerable keys = m_Membership.Keys.Where(k => k.Key.EndsWith(principalID.ToString())); + return keys.Where(m_Membership.ContainsKey).Select(x => m_Membership[x]).ToArray(); + } + + public MembershipData[] RetrievePrincipalGroupMemberships(string principalID) + { + return RetrieveMemberships(principalID); + } + + public MembershipData RetrievePrincipalGroupMembership(string principalID, UUID groupID) + { + CompositeKey dkey = new CompositeKey(groupID, principalID); + if (m_Membership.ContainsKey(dkey)) + return m_Membership[dkey]; + return null; + } + + public bool StoreMember(MembershipData data) + { + CompositeKey dkey = new CompositeKey(data.GroupID, data.PrincipalID); + m_Membership[dkey] = data; + return true; + } + + public bool DeleteMember(UUID groupID, string principalID) + { + CompositeKey dkey = new CompositeKey(groupID, principalID); + if (m_Membership.ContainsKey(dkey)) + return m_Membership.Remove(dkey); + + return false; + } + + public int MemberCount(UUID groupID) + { + return m_Membership.Count; + } + #endregion + + #region roles table + public bool StoreRole(RoleData data) + { + CompositeKey dkey = new CompositeKey(data.GroupID, data.RoleID.ToString()); + m_Roles[dkey] = data; + return true; + } + + public RoleData RetrieveRole(UUID groupID, UUID roleID) + { + CompositeKey dkey = new CompositeKey(groupID, roleID.ToString()); + if (m_Roles.ContainsKey(dkey)) + return m_Roles[dkey]; + + return null; + } + + public RoleData[] RetrieveRoles(UUID groupID) + { + IEnumerable keys = m_Roles.Keys.Where(k => k.Key.StartsWith(groupID.ToString())); + return keys.Where(m_Roles.ContainsKey).Select(x => m_Roles[x]).ToArray(); + } + + public bool DeleteRole(UUID groupID, UUID roleID) + { + CompositeKey dkey = new CompositeKey(groupID, roleID.ToString()); + if (m_Roles.ContainsKey(dkey)) + return m_Roles.Remove(dkey); + + return false; + } + + public int RoleCount(UUID groupID) + { + return m_Roles.Count; + } + #endregion + + #region rolememberhip table + public RoleMembershipData[] RetrieveRolesMembers(UUID groupID) + { + IEnumerable keys = m_Roles.Keys.Where(k => k.Key.StartsWith(groupID.ToString())); + return keys.Where(m_RoleMembership.ContainsKey).Select(x => m_RoleMembership[x]).ToArray(); + } + + public RoleMembershipData[] RetrieveRoleMembers(UUID groupID, UUID roleID) + { + IEnumerable keys = m_Roles.Keys.Where(k => k.Key.StartsWith(groupID.ToString() + roleID.ToString())); + return keys.Where(m_RoleMembership.ContainsKey).Select(x => m_RoleMembership[x]).ToArray(); + } + + public RoleMembershipData[] RetrieveMemberRoles(UUID groupID, string principalID) + { + IEnumerable keys = m_Roles.Keys.Where(k => k.Key.StartsWith(groupID.ToString()) && k.Key.EndsWith(principalID)); + return keys.Where(m_RoleMembership.ContainsKey).Select(x => m_RoleMembership[x]).ToArray(); + } + + public RoleMembershipData RetrieveRoleMember(UUID groupID, UUID roleID, string principalID) + { + CompositeKey dkey = new CompositeKey(groupID, roleID.ToString(), principalID); + if (m_RoleMembership.ContainsKey(dkey)) + return m_RoleMembership[dkey]; + + return null; + } + + public int RoleMemberCount(UUID groupID, UUID roleID) + { + return m_RoleMembership.Count; + } + + public bool StoreRoleMember(RoleMembershipData data) + { + CompositeKey dkey = new CompositeKey(data.GroupID, data.RoleID.ToString(), data.PrincipalID); + m_RoleMembership[dkey] = data; + return true; + } + + public bool DeleteRoleMember(RoleMembershipData data) + { + CompositeKey dkey = new CompositeKey(data.GroupID, data.RoleID.ToString(), data.PrincipalID); + if (m_RoleMembership.ContainsKey(dkey)) + return m_RoleMembership.Remove(dkey); + + return false; + } + + public bool DeleteMemberAllRoles(UUID groupID, string principalID) + { + List keys = m_RoleMembership.Keys.Where(k => k.Key.StartsWith(groupID.ToString()) && k.Key.EndsWith(principalID)).ToList(); + foreach (CompositeKey k in keys) + m_RoleMembership.Remove(k); + return true; + } + #endregion + + #region principals table + public bool StorePrincipal(PrincipalData data) + { + m_Principals[data.PrincipalID] = data; + return true; + } + + public PrincipalData RetrievePrincipal(string principalID) + { + if (m_Principals.ContainsKey(principalID)) + return m_Principals[principalID]; + + return null; + } + + public bool DeletePrincipal(string principalID) + { + if (m_Principals.ContainsKey(principalID)) + return m_Principals.Remove(principalID); + return false; + } + #endregion + + #region invites table + public bool StoreInvitation(InvitationData data) + { + return false; + } + + public InvitationData RetrieveInvitation(UUID inviteID) + { + return null; + } + + public InvitationData RetrieveInvitation(UUID groupID, string principalID) + { + return null; + } + + public bool DeleteInvite(UUID inviteID) + { + return false; + } + + public void DeleteOldInvites() + { + } + #endregion + + #region notices table + public bool StoreNotice(NoticeData data) + { + return false; + } + + public NoticeData RetrieveNotice(UUID noticeID) + { + return null; + } + + public NoticeData[] RetrieveNotices(UUID groupID) + { + return new NoticeData[0]; + } + + public bool DeleteNotice(UUID noticeID) + { + return false; + } + + public void DeleteOldNotices() + { + } + #endregion + + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/TestHttpClientContext.cs b/Tests/OpenSim.Tests.Common/Mock/TestHttpClientContext.cs new file mode 100644 index 00000000000..72938304921 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestHttpClientContext.cs @@ -0,0 +1,112 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Net.Sockets; +using System.Text; +using OSHttpServer; +using OpenSim.Framework; + +namespace OpenSim.Tests.Common +{ +/* + public class TestHttpClientContext: IHttpClientContext + { + /// + /// Bodies of responses from the server. + /// + public string ResponseBody + { + get { return Encoding.UTF8.GetString(m_responseStream.ToArray()); } + } + + public Byte[] ResponseBodyBytes + { + get{ return m_responseStream.ToArray(); } + } + + private MemoryStream m_responseStream = new MemoryStream(); + + public bool IsSecured { get; set; } + + public bool Secured + { + get { return IsSecured; } + set { IsSecured = value; } + } + + public TestHttpClientContext(bool secured) + { + Secured = secured; + } + + public void Disconnect(SocketError error) + { +// Console.WriteLine("TestHttpClientContext.Disconnect Received disconnect with status {0}", error); + } + + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body) {Console.WriteLine("x");} + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason) {Console.WriteLine("xx");} + public void Respond(string body) { Console.WriteLine("xxx");} + + public void Send(byte[] buffer) + { + // Getting header data here +// Console.WriteLine("xxxx: Got {0}", Encoding.UTF8.GetString(buffer)); + } + + public void Send(byte[] buffer, int offset, int size) + { +// Util.PrintCallStack(); +// +// Console.WriteLine( +// "TestHttpClientContext.Send(byte[], int, int) got offset={0}, size={1}, buffer={2}", +// offset, size, Encoding.UTF8.GetString(buffer)); + + m_responseStream.Write(buffer, offset, size); + } + + public void Respond(string httpVersion, HttpStatusCode statusCode, string reason, string body, string contentType) {Console.WriteLine("xxxxxx");} + public void Close() { } + public bool EndWhenDone { get { return false;} set { return;}} + + public HTTPNetworkContext GiveMeTheNetworkStreamIKnowWhatImDoing() + { + return new HTTPNetworkContext(); + } + + public event EventHandler Disconnected = delegate { }; + /// + /// A request have been received in the context. + /// + public event EventHandler RequestReceived = delegate { }; + } +*/ +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestHttpRequest.cs b/Tests/OpenSim.Tests.Common/Mock/TestHttpRequest.cs new file mode 100644 index 00000000000..b69c70db5cd --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestHttpRequest.cs @@ -0,0 +1,175 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Specialized; +using System.IO; +using OSHttpServer; + +namespace OpenSim.Tests.Common +{ +/* + public class TestHttpRequest: IHttpRequest + { + private string _uriPath; + public bool BodyIsComplete + { + get { return true; } + } + public string[] AcceptTypes + { + get {return _acceptTypes; } + } + private string[] _acceptTypes; + public Stream Body + { + get { return _body; } + set { _body = value;} + } + private Stream _body; + public ConnectionType Connection + { + get { return _connection; } + set { _connection = value; } + } + private ConnectionType _connection; + public int ContentLength + { + get { return _contentLength; } + set { _contentLength = value; } + } + private int _contentLength; + public NameValueCollection Headers + { + get { return _headers; } + } + private NameValueCollection _headers = new NameValueCollection(); + + public string HttpVersion { get; set; } + + public string Method + { + get { return _method; } + set { _method = value; } + } + private string _method = null; + public HttpInput QueryString + { + get { return _queryString; } + } + private HttpInput _queryString = null; + public Uri Uri + { + get { return _uri; } + set { _uri = value; } + } + private Uri _uri = null; + public string[] UriParts + { + get { return _uri.Segments; } + } + public HttpParam Param + { + get { return null; } + } + public HttpForm Form + { + get { return null; } + } + public bool IsAjax + { + get { return false; } + } + public RequestCookies Cookies + { + get { return null; } + } + + public TestHttpRequest() + { + HttpVersion = "HTTP/1.1"; + } + + public TestHttpRequest(string contentEncoding, string contentType, string userAgent, + string remoteAddr, string remotePort, string[] acceptTypes, + ConnectionType connectionType, int contentLength, Uri uri) : base() + { + _headers["content-encoding"] = contentEncoding; + _headers["content-type"] = contentType; + _headers["user-agent"] = userAgent; + _headers["remote_addr"] = remoteAddr; + _headers["remote_port"] = remotePort; + + _acceptTypes = acceptTypes; + _connection = connectionType; + _contentLength = contentLength; + _uri = uri; + } + + public void DecodeBody(FormDecoderProvider providers) {} + public void SetCookies(RequestCookies cookies) {} + public void AddHeader(string name, string value) + { + _headers.Add(name, value); + } + public int AddToBody(byte[] bytes, int offset, int length) + { + return 0; + } + public void Clear() {} + + public object Clone() + { + TestHttpRequest clone = new TestHttpRequest(); + clone._acceptTypes = _acceptTypes; + clone._connection = _connection; + clone._contentLength = _contentLength; + clone._uri = _uri; + clone._headers = new NameValueCollection(_headers); + + return clone; + } + public IHttpResponse CreateResponse(IHttpClientContext context) + { + return new HttpResponse(context, this); + } + /// + /// Path and query (will be merged with the host header) and put in Uri + /// + /// + public string UriPath + { + get { return _uriPath; } + set + { + _uriPath = value; + + } + } + } +*/ +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestHttpResponse.cs b/Tests/OpenSim.Tests.Common/Mock/TestHttpResponse.cs new file mode 100644 index 00000000000..f6f789819c9 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestHttpResponse.cs @@ -0,0 +1,173 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.IO; +using System.Net; +using System.Text; +using OSHttpServer; + +namespace OpenSim.Tests.Common +{ +/* + public class TestHttpResponse: IHttpResponse + { + public Stream Body + { + get { return _body; } + + set { _body = value; } + } + private Stream _body; + + public string ProtocolVersion + { + get { return _protocolVersion; } + set { _protocolVersion = value; } + } + private string _protocolVersion; + + public bool Chunked + { + get { return _chunked; } + + set { _chunked = value; } + } + private bool _chunked; + + public ConnectionType Connection + { + get { return _connection; } + + set { _connection = value; } + } + private ConnectionType _connection; + + public Encoding Encoding + { + get { return _encoding; } + + set { _encoding = value; } + } + private Encoding _encoding; + + public int KeepAlive + { + get { return _keepAlive; } + + set { _keepAlive = value; } + } + private int _keepAlive; + + public HttpStatusCode Status + { + get { return _status; } + + set { _status = value; } + } + private HttpStatusCode _status; + + public string Reason + { + get { return _reason; } + + set { _reason = value; } + } + private string _reason; + + public long ContentLength + { + get { return _contentLength; } + + set { _contentLength = value; } + } + private long _contentLength; + + public string ContentType + { + get { return _contentType; } + + set { _contentType = value; } + } + private string _contentType; + + public bool HeadersSent + { + get { return _headersSent; } + } + private bool _headersSent; + + public bool Sent + { + get { return _sent; } + } + private bool _sent; + + public ResponseCookies Cookies + { + get { return _cookies; } + } + private ResponseCookies _cookies = null; + + public TestHttpResponse() + { + _headersSent = false; + _sent = false; + } + + public void AddHeader(string name, string value) {} + + public void Send() + { + if (!_headersSent) SendHeaders(); + if (_sent) throw new InvalidOperationException("stuff already sent"); + _sent = true; + } + + public void SendBody(byte[] buffer, int offset, int count) + { + if (!_headersSent) SendHeaders(); + _sent = true; + } + + public void SendBody(byte[] buffer) + { + if (!_headersSent) SendHeaders(); + _sent = true; + } + + public void SendHeaders() + { + if (_headersSent) throw new InvalidOperationException("headers already sent"); + _headersSent = true; + } + + public void Redirect(Uri uri) {} + public void Redirect(string url) {} + } +*/ +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestInventoryDataPlugin.cs b/Tests/OpenSim.Tests.Common/Mock/TestInventoryDataPlugin.cs new file mode 100644 index 00000000000..65965017e06 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestInventoryDataPlugin.cs @@ -0,0 +1,216 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Framework; +using OpenSim.Data; + +namespace OpenSim.Tests.Common +{ + /// + /// In memory inventory data plugin for test purposes. Could be another dll when properly filled out and when the + /// mono addin plugin system starts co-operating with the unit test system. Currently no locking since unit + /// tests are single threaded. + /// + public class TestInventoryDataPlugin : IInventoryDataPlugin + { +// private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// + /// Inventory folders + /// + private Dictionary m_folders = new Dictionary(); + + //// + /// Inventory items + /// + private Dictionary m_items = new Dictionary(); + + /// + /// User root folders + /// + private Dictionary m_rootFolders = new Dictionary(); + + public string Version { get { return "0"; } } + public string Name { get { return "TestInventoryDataPlugin"; } } + + public void Initialise() {} + public void Initialise(string connect) {} + public void Dispose() {} + + public List getFolderHierarchy(UUID parentID) + { + List folders = new List(); + + foreach (InventoryFolderBase folder in m_folders.Values) + { + if (folder.ParentID == parentID) + { + folders.AddRange(getFolderHierarchy(folder.ID)); + folders.Add(folder); + } + } + + return folders; + } + + public List getInventoryInFolder(UUID folderID) + { +// InventoryFolderBase folder = m_folders[folderID]; + +// m_log.DebugFormat("[MOCK INV DB]: Getting items in folder {0} {1}", folder.Name, folder.ID); + + List items = new List(); + + foreach (InventoryItemBase item in m_items.Values) + { + if (item.Folder == folderID) + { +// m_log.DebugFormat("[MOCK INV DB]: getInventoryInFolder() adding item {0}", item.Name); + items.Add(item); + } + } + + return items; + } + + public List getUserRootFolders(UUID user) { return null; } + + public InventoryFolderBase getUserRootFolder(UUID user) + { +// m_log.DebugFormat("[MOCK INV DB]: Looking for root folder for {0}", user); + + InventoryFolderBase folder = null; + m_rootFolders.TryGetValue(user, out folder); + + return folder; + } + + public List getInventoryFolders(UUID parentID) + { +// InventoryFolderBase parentFolder = m_folders[parentID]; + +// m_log.DebugFormat("[MOCK INV DB]: Getting folders in folder {0} {1}", parentFolder.Name, parentFolder.ID); + + List folders = new List(); + + foreach (InventoryFolderBase folder in m_folders.Values) + { + if (folder.ParentID == parentID) + { +// m_log.DebugFormat( +// "[MOCK INV DB]: Found folder {0} {1} in {2} {3}", +// folder.Name, folder.ID, parentFolder.Name, parentFolder.ID); + + folders.Add(folder); + } + } + + return folders; + } + + public InventoryFolderBase getInventoryFolder(UUID folderId) + { + InventoryFolderBase folder = null; + m_folders.TryGetValue(folderId, out folder); + + return folder; + } + + public InventoryFolderBase queryInventoryFolder(UUID folderID) + { + return getInventoryFolder(folderID); + } + + public void addInventoryFolder(InventoryFolderBase folder) + { +// m_log.DebugFormat( +// "[MOCK INV DB]: Adding inventory folder {0} {1} type {2}", +// folder.Name, folder.ID, (AssetType)folder.Type); + + m_folders[folder.ID] = folder; + + if (folder.ParentID.IsZero()) + { +// m_log.DebugFormat( +// "[MOCK INV DB]: Adding root folder {0} {1} for {2}", folder.Name, folder.ID, folder.Owner); + m_rootFolders[folder.Owner] = folder; + } + } + + public void updateInventoryFolder(InventoryFolderBase folder) + { + m_folders[folder.ID] = folder; + } + + public void moveInventoryFolder(InventoryFolderBase folder) + { + // Simple replace + updateInventoryFolder(folder); + } + + public void deleteInventoryFolder(UUID folderId) + { + if (m_folders.ContainsKey(folderId)) + m_folders.Remove(folderId); + } + + public void addInventoryItem(InventoryItemBase item) + { + InventoryFolderBase folder = m_folders[item.Folder]; + +// m_log.DebugFormat( +// "[MOCK INV DB]: Adding inventory item {0} {1} in {2} {3}", item.Name, item.ID, folder.Name, folder.ID); + + m_items[item.ID] = item; + } + + public void updateInventoryItem(InventoryItemBase item) { addInventoryItem(item); } + + public void deleteInventoryItem(UUID itemId) + { + if (m_items.ContainsKey(itemId)) + m_items.Remove(itemId); + } + + public InventoryItemBase getInventoryItem(UUID itemId) + { + if (m_items.ContainsKey(itemId)) + return m_items[itemId]; + else + return null; + } + + public InventoryItemBase queryInventoryItem(UUID item) + { + return null; + } + + public List fetchActiveGestures(UUID avatarID) { return null; } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/TestLLUDPServer.cs b/Tests/OpenSim.Tests.Common/Mock/TestLLUDPServer.cs new file mode 100644 index 00000000000..7a6663319cb --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestLLUDPServer.cs @@ -0,0 +1,169 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Net; +using Nini.Config; +using OpenMetaverse.Packets; + +using OpenSim.Framework; +using OpenSim.Region.ClientStack.LindenUDP; + +namespace OpenSim.Tests.Common +{ + /// + /// This class enables regression testing of the LLUDPServer by allowing us to intercept outgoing data. + /// + public class TestLLUDPServer : LLUDPServer + { + public List PacketsSent { get; private set; } + + public TestLLUDPServer(IPAddress listenIP, uint port, int proxyPortOffsetParm, IConfigSource configSource, AgentCircuitManager circuitManager) + : base(listenIP, port, proxyPortOffsetParm, configSource, circuitManager) + { + PacketsSent = new List(); + } + + public override void SendAckImmediate(IPEndPoint remoteEndpoint, PacketAckPacket ack) + { + PacketsSent.Add(ack); + } + + public override void SendPacket( + LLUDPClient udpClient, Packet packet, ThrottleOutPacketType category, bool allowSplitting, UnackedPacketMethod method) + { + PacketsSent.Add(packet); + } + + public void ClientOutgoingPacketHandler(IClientAPI client, bool resendUnacked, bool sendAcks, bool sendPing) + { + m_resendUnacked = resendUnacked; + m_sendAcks = sendAcks; + m_sendPing = sendPing; + + ClientOutgoingPacketHandler(client); + } + +//// /// +//// /// The chunks of data to pass to the LLUDPServer when it calls EndReceive +//// /// +//// protected Queue m_chunksToLoad = new Queue(); +// +//// protected override void BeginReceive() +//// { +//// if (m_chunksToLoad.Count > 0 && m_chunksToLoad.Peek().BeginReceiveException) +//// { +//// ChunkSenderTuple tuple = m_chunksToLoad.Dequeue(); +//// reusedEpSender = tuple.Sender; +//// throw new SocketException(); +//// } +//// } +// +//// protected override bool EndReceive(out int numBytes, IAsyncResult result, ref EndPoint epSender) +//// { +//// numBytes = 0; +//// +//// //m_log.Debug("Queue size " + m_chunksToLoad.Count); +//// +//// if (m_chunksToLoad.Count <= 0) +//// return false; +//// +//// ChunkSenderTuple tuple = m_chunksToLoad.Dequeue(); +//// RecvBuffer = tuple.Data; +//// numBytes = tuple.Data.Length; +//// epSender = tuple.Sender; +//// +//// return true; +//// } +// +//// public override void SendPacketTo(byte[] buffer, int size, SocketFlags flags, uint circuitcode) +//// { +//// // Don't do anything just yet +//// } +// +// /// +// /// Signal that this chunk should throw an exception on Socket.BeginReceive() +// /// +// /// +// public void LoadReceiveWithBeginException(EndPoint epSender) +// { +// ChunkSenderTuple tuple = new ChunkSenderTuple(epSender); +// tuple.BeginReceiveException = true; +// m_chunksToLoad.Enqueue(tuple); +// } +// +// /// +// /// Load some data to be received by the LLUDPServer on the next receive call +// /// +// /// +// /// +// public void LoadReceive(byte[] data, EndPoint epSender) +// { +// m_chunksToLoad.Enqueue(new ChunkSenderTuple(data, epSender)); +// } +// +// /// +// /// Load a packet to be received by the LLUDPServer on the next receive call +// /// +// /// +// public void LoadReceive(Packet packet, EndPoint epSender) +// { +// LoadReceive(packet.ToBytes(), epSender); +// } +// +// /// +// /// Calls the protected asynchronous result method. This fires out all data chunks currently queued for send +// /// +// /// +// public void ReceiveData(IAsyncResult result) +// { +// // Doesn't work the same way anymore +//// while (m_chunksToLoad.Count > 0) +//// OnReceivedData(result); +// } + } + + /// + /// Record the data and sender tuple + /// + public class ChunkSenderTuple + { + public byte[] Data; + public EndPoint Sender; + public bool BeginReceiveException; + + public ChunkSenderTuple(byte[] data, EndPoint sender) + { + Data = data; + Sender = sender; + } + + public ChunkSenderTuple(EndPoint sender) + { + Sender = sender; + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/TestLandChannel.cs b/Tests/OpenSim.Tests.Common/Mock/TestLandChannel.cs new file mode 100644 index 00000000000..38a4a2a476d --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestLandChannel.cs @@ -0,0 +1,130 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Collections.Generic; +using OpenMetaverse; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Region.CoreModules.World.Land; + +namespace OpenSim.Tests.Common +{ + /// + /// Land channel for test purposes + /// + public class TestLandChannel : ILandChannel + { + private Scene m_scene; + private List m_parcels; + + public float BanLineSafeHeight { get { return 100f; } } + + public TestLandChannel(Scene scene) + { + m_scene = scene; + m_parcels = new List(); + SetupDefaultParcel(); + } + + private void SetupDefaultParcel() + { + ILandObject obj = new LandObject(UUID.Zero, false, m_scene); + obj.LandData.Name = "Your Parcel"; + m_parcels.Add(obj); + } + + public List ParcelsNearPoint(Vector3 position) + { + return new List(); + } + + public List AllParcels() + { + return m_parcels; + } + + public void Clear(bool setupDefaultParcel) + { + m_parcels.Clear(); + + if (setupDefaultParcel) + SetupDefaultParcel(); + } + + protected ILandObject GetNoLand() + { + ILandObject obj = new LandObject(UUID.Zero, false, m_scene); + obj.LandData.Name = "NO LAND"; + return obj; + } + + public ILandObject GetLandObject(Vector3 position) + { + return GetLandObject(position.X, position.Y); + } + + public ILandObject GetLandObject(int x, int y) + { + return GetNoLand(); + } + + public ILandObject GetLandObjectClippedXY(float x, float y) + { + return GetNoLand(); + } + + public ILandObject GetLandObject(int localID) + { + return GetNoLand(); + } + + public ILandObject GetLandObject(UUID ID) + { + return GetNoLand(); + } + + public ILandObject GetLandObject(float x, float y) + { + return GetNoLand(); + } + + public bool IsLandPrimCountTainted() { return false; } + public bool IsForcefulBansAllowed() { return false; } + public void UpdateLandObject(int localID, LandData data) {} + public void SendParcelsOverlay(IClientAPI client) {} + public void ReturnObjectsInParcel(int localID, uint returnType, UUID[] agentIDs, UUID[] taskIDs, IClientAPI remoteClient) {} + public void setParcelObjectMaxOverride(overrideParcelMaxPrimCountDelegate overrideDel) {} + public void setSimulatorObjectMaxOverride(overrideSimulatorMaxPrimCountDelegate overrideDel) {} + public void SetParcelOtherCleanTime(IClientAPI remoteClient, int localID, int otherCleanTime) {} + + public void Join(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id) {} + public void Subdivide(int start_x, int start_y, int end_x, int end_y, UUID attempting_user_id) {} + public void sendClientInitialLandInfo(IClientAPI remoteClient, bool overlay) { } + public void ClearAllEnvironments(){ } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestOSHttpRequest.cs b/Tests/OpenSim.Tests.Common/Mock/TestOSHttpRequest.cs new file mode 100644 index 00000000000..722ab704787 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestOSHttpRequest.cs @@ -0,0 +1,192 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections; +using System.Collections.Generic; +using System.Collections.Specialized; +using System.IO; +using System.Net; +using System.Text; +using System.Web; +using OpenSim.Framework.Servers.HttpServer; + +namespace OpenSim.Tests.Common +{ + public class TestOSHttpRequest : IOSHttpRequest + { + public string[] AcceptTypes + { + get + { + throw new NotImplementedException (); + } + } + + public Encoding ContentEncoding + { + get + { + throw new NotImplementedException (); + } + } + + public long ContentLength + { + get + { + throw new NotImplementedException (); + } + } + + public long ContentLength64 + { + get + { + throw new NotImplementedException (); + } + } + + public string ContentType + { + get + { + throw new NotImplementedException (); + } + } + + + public bool HasEntityBody + { + get + { + throw new NotImplementedException (); + } + } + + public NameValueCollection Headers { get; set; } + + public string HttpMethod + { + get + { + throw new NotImplementedException (); + } + } + + public Stream InputStream { get; set;} + + public bool IsSecured + { + get + { + throw new NotImplementedException (); + } + } + + public bool KeepAlive + { + get + { + throw new NotImplementedException (); + } + } + + public NameValueCollection QueryString + { + get + { + throw new NotImplementedException (); + } + } + + public Hashtable Query + { + get + { + throw new NotImplementedException(); + } + } + + public Dictionary QueryAsDictionary + { + get + { + throw new NotImplementedException(); + } + } + + public HashSet QueryFlags + { + get + { + throw new NotImplementedException(); + } + } + + public string RawUrl + { + get + { + throw new NotImplementedException (); + } + } + + public IPEndPoint RemoteIPEndPoint + { + get + { + throw new NotImplementedException(); + } + } + + public IPEndPoint LocalIPEndPoint + { + get + { + throw new NotImplementedException(); + } + } + + public Uri Url { get; set; } + public string UriPath { get;} + public double ArrivalTS { get; } + + public string UserAgent + { + get + { + throw new NotImplementedException (); + } + } + + public TestOSHttpRequest() + { + Headers = new NameValueCollection(); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestOSHttpResponse.cs b/Tests/OpenSim.Tests.Common/Mock/TestOSHttpResponse.cs new file mode 100644 index 00000000000..6afcb05d8ab --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestOSHttpResponse.cs @@ -0,0 +1,139 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Text; +using OpenSim.Framework.Servers.HttpServer; + +namespace OpenSim.Tests.Common +{ + public class TestOSHttpResponse : IOSHttpResponse + { + /// + /// Content type property. + /// + /// + /// Setting this property will also set IsContentTypeSet to + /// true. + /// + public string ContentType { get; set; } + + /// + /// Boolean property indicating whether the content type + /// property actively has been set. + /// + /// + /// IsContentTypeSet will go away together with .NET base. + /// + // public bool IsContentTypeSet + // { + // get { return _contentTypeSet; } + // } + // private bool _contentTypeSet; + + /// + /// Length of the body content; 0 if there is no body. + /// + public long ContentLength { get; set; } + + /// + /// Alias for ContentLength. + /// + public long ContentLength64 { get; set; } + + public int Priority { get; set; } + public byte[] RawBuffer { get; set; } + public int RawBufferStart { get; set; } + public int RawBufferLen { get; set; } + + /// + /// Encoding of the body content. + /// + public Encoding ContentEncoding { get; set; } + + public bool KeepAlive { get; set; } + + /// + /// Get or set the keep alive timeout property (default is + /// 20). Setting this to 0 also disables KeepAlive. Setting + /// this to something else but 0 also enable KeepAlive. + /// + public int KeepAliveTimeout { get; set; } + + /// + /// Return the output stream feeding the body. + /// + /// + /// On its way out... + /// + public Stream OutputStream { get; private set; } + + public string ProtocolVersion { get; set; } + + /// + /// Return the output stream feeding the body. + /// + public Stream Body { get; private set; } + + /// + /// Chunk transfers. + /// + public bool SendChunked { get; set; } + + /// + /// HTTP status code. + /// + public int StatusCode { get; set; } + + /// + /// HTTP status description. + /// + public string StatusDescription { get; set; } + + public double RequestTS { get; } + + /// + /// Set response as a http redirect + /// + /// redirection target url + /// the response Status, must be Redirect, Moved,MovedPermanently,RedirectKeepVerb, RedirectMethod, TemporaryRedirect. Defaults to Redirect + public void Redirect(string url, HttpStatusCode redirStatusCode = HttpStatusCode.Redirect) { throw new NotImplementedException(); } + /// + /// Add a header field and content to the response. + /// + /// string containing the header field + /// name + /// string containing the header field + /// value + public void AddHeader(string key, string value) { throw new NotImplementedException(); } + + public void Send() { } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/Mock/TestScene.cs b/Tests/OpenSim.Tests.Common/Mock/TestScene.cs new file mode 100644 index 00000000000..0f4f48acada --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestScene.cs @@ -0,0 +1,72 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; +using OpenSim.Services.Interfaces; + +namespace OpenSim.Tests.Common +{ + public class TestScene : Scene + { + public TestScene( + RegionInfo regInfo, AgentCircuitManager authen, + ISimulationDataService simDataService, IEstateDataService estateDataService, + IConfigSource config, string simulatorVersion) + : base(regInfo, authen, simDataService, estateDataService, + config, simulatorVersion) + { + } + + ~TestScene() + { + //Console.WriteLine("TestScene destructor called for {0}", RegionInfo.RegionName); + Console.WriteLine("TestScene destructor called"); + } + + /// + /// Temporarily override session authentication for tests (namely teleport). + /// + /// + /// TODO: This needs to be mocked out properly. + /// + /// + /// + public override bool VerifyUserPresence(AgentCircuitData agent, out string reason) + { + reason = String.Empty; + return true; + } + + public AsyncSceneObjectGroupDeleter SceneObjectGroupDeleter + { + get { return m_asyncSceneObjectDeleter; } + } + } +} diff --git a/Tests/OpenSim.Tests.Common/Mock/TestXInventoryDataPlugin.cs b/Tests/OpenSim.Tests.Common/Mock/TestXInventoryDataPlugin.cs new file mode 100644 index 00000000000..ec23da2ca39 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/Mock/TestXInventoryDataPlugin.cs @@ -0,0 +1,197 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenMetaverse; + +using OpenSim.Data; +using OpenSim.Data.Null; + +namespace OpenSim.Tests.Common +{ + public class TestXInventoryDataPlugin : NullGenericDataHandler, IXInventoryData + { + private Dictionary m_allFolders = new Dictionary(); + private Dictionary m_allItems = new Dictionary(); + + public TestXInventoryDataPlugin(string conn, string realm) {} + + public XInventoryItem[] GetItems(string field, string val) + { +// Console.WriteLine( +// "Requesting items, fields {0}, vals {1}", string.Join(", ", fields), string.Join(", ", vals)); + + List origItems = Get(field, val, m_allItems.Values.ToList()); + + XInventoryItem[] items = origItems.Select(i => i.Clone()).ToArray(); + +// Console.WriteLine("Found {0} items", items.Length); +// Array.ForEach(items, i => Console.WriteLine("Found item {0} {1}", i.inventoryName, i.inventoryID)); + + return items; + } + + public XInventoryItem[] GetItems(string field, string[] vals) + { +// Console.WriteLine( +// "Requesting items, fields {0}, vals {1}", string.Join(", ", fields), string.Join(", ", vals)); + + List origItems = Get(field, vals, m_allItems.Values.ToList()); + + XInventoryItem[] items = origItems.Select(i => i.Clone()).ToArray(); + +// Console.WriteLine("Found {0} items", items.Length); +// Array.ForEach(items, i => Console.WriteLine("Found item {0} {1}", i.inventoryName, i.inventoryID)); + + return items; + } + + public XInventoryItem[] GetItems(string[] fields, string[] vals) + { +// Console.WriteLine( +// "Requesting items, fields {0}, vals {1}", string.Join(", ", fields), string.Join(", ", vals)); + + List origItems = Get(fields, vals, m_allItems.Values.ToList()); + + XInventoryItem[] items = origItems.Select(i => i.Clone()).ToArray(); + +// Console.WriteLine("Found {0} items", items.Length); +// Array.ForEach(items, i => Console.WriteLine("Found item {0} {1}", i.inventoryName, i.inventoryID)); + + return items; + } + + public XInventoryFolder[] GetFolders(string[] fields, string[] vals) + { +// Console.WriteLine( +// "Requesting folders, fields {0}, vals {1}", string.Join(", ", fields), string.Join(", ", vals)); + + List origFolders + = Get(fields, vals, m_allFolders.Values.ToList()); + + XInventoryFolder[] folders = origFolders.Select(f => f.Clone()).ToArray(); + +// Console.WriteLine("Found {0} folders", folders.Length); +// Array.ForEach(folders, f => Console.WriteLine("Found folder {0} {1}", f.folderName, f.folderID)); + + return folders; + } + + public bool StoreFolder(XInventoryFolder folder) + { + m_allFolders[folder.folderID] = folder.Clone(); + +// Console.WriteLine("Added folder {0} {1}", folder.folderName, folder.folderID); + + return true; + } + + public bool StoreItem(XInventoryItem item) + { + m_allItems[item.inventoryID] = item.Clone(); + +// Console.WriteLine( +// "Added item {0} {1}, folder {2}, creator {3}, owner {4}", +// item.inventoryName, item.inventoryID, item.parentFolderID, item.creatorID, item.avatarID); + + return true; + } + + public bool DeleteFolders(string field, string val) + { + return DeleteFolders(new string[] { field }, new string[] { val }); + } + + public bool DeleteFolders(string[] fields, string[] vals) + { + XInventoryFolder[] foldersToDelete = GetFolders(fields, vals); + Array.ForEach(foldersToDelete, f => m_allFolders.Remove(f.folderID)); + + return true; + } + + public bool DeleteItems(string field, string val) + { + return DeleteItems(new string[] { field }, new string[] { val }); + } + + public bool DeleteItems(string[] fields, string[] vals) + { + XInventoryItem[] itemsToDelete = GetItems(fields, vals); + Array.ForEach(itemsToDelete, i => m_allItems.Remove(i.inventoryID)); + + return true; + } + + public bool MoveItem(string id, string newParent) + { + UUID uid = new UUID(id); + UUID upid = new UUID(newParent); + m_allItems[uid].parentFolderID = upid; + return true; + } + + public bool MoveItems(string[] ids, string[] newParents) + { + if(ids.Length != newParents.Length) + return false; + for(int i =0; i< ids.Length;++i) + MoveItem(ids[i], newParents[i]); + return true; + } + + public bool MoveFolder(string id, string newParent) + { + // Don't use GetFolders() here - it takes a clone! + XInventoryFolder folder = m_allFolders[new UUID(id)]; + + if (folder == null) + return false; + + folder.parentFolderID = new UUID(newParent); + +// XInventoryFolder[] newParentFolders +// = GetFolders(new string[] { "folderID" }, new string[] { folder.parentFolderID.ToString() }); + +// Console.WriteLine( +// "Moved folder {0} {1}, to {2} {3}", +// folder.folderName, folder.folderID, newParentFolders[0].folderName, folder.parentFolderID); + + // TODO: Really need to implement folder version incrementing, though this should be common code anyway, + // not reimplemented in each db plugin. + + return true; + } + + public XInventoryItem[] GetActiveGestures(UUID principalID) { throw new NotImplementedException(); } + public int GetAssetPermissions(UUID principalID, UUID assetID) { throw new NotImplementedException(); } + + public XInventoryFolder[] GetFolder(string field, string val) + { + throw new NotImplementedException(); + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/OpenSim.Tests.Common.csproj b/Tests/OpenSim.Tests.Common/OpenSim.Tests.Common.csproj new file mode 100644 index 00000000000..db861ac9fdb --- /dev/null +++ b/Tests/OpenSim.Tests.Common/OpenSim.Tests.Common.csproj @@ -0,0 +1,62 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + + + + + + + ..\..\bin\OpenMetaverse.dll + False + + + ..\..\bin\OpenMetaverse.StructuredData.dll + False + + + ..\..\bin\OpenMetaverseTypes.dll + False + + + ..\..\bin\Nini.dll + False + + + + + + + + + + + + + + + + + + diff --git a/Tests/OpenSim.Tests.Common/OpenSimTestCase.cs b/Tests/OpenSim.Tests.Common/OpenSimTestCase.cs new file mode 100644 index 00000000000..dc261da269b --- /dev/null +++ b/Tests/OpenSim.Tests.Common/OpenSimTestCase.cs @@ -0,0 +1,55 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using OpenSim.Framework.Servers; + +namespace OpenSim.Tests.Common +{ + public class OpenSimTestCase : IDisposable + { + protected OpenSimTestCase() + { + //TestHelpers.InMethod(); + // Disable logging for each test so that one where logging is enabled doesn't cause all subsequent tests + // to have logging on if it failed with an exception. + TestHelpers.DisableLogging(); + + // This is an unfortunate bit of clean up we have to do because MainServer manages things through static + // variables and the VM is not restarted between tests. + if (MainServer.Instance != null) + { + MainServer.RemoveHttpServer(MainServer.Instance.Port); + // MainServer.Instance = null; + } + } + + public virtual void Dispose() + { + // Do "global" teardown here; Called after every test method. + } + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests.Common/TestHelpers.cs b/Tests/OpenSim.Tests.Common/TestHelpers.cs new file mode 100644 index 00000000000..84e93c0df2c --- /dev/null +++ b/Tests/OpenSim.Tests.Common/TestHelpers.cs @@ -0,0 +1,112 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Diagnostics; +using OpenMetaverse; + +namespace OpenSim.Tests.Common +{ + public class TestHelpers + { + /// + /// A debugging method that can be used to print out which test method you are in + /// + public static void InMethod() + { + StackTrace stackTrace = new StackTrace(); + Console.WriteLine(); + Console.WriteLine("===> In Test Method : {0} <===", stackTrace.GetFrame(1).GetMethod().Name); + } + + public static void EnableLogging() + { + // TODO: enable logging while testing in the Microsoft.Extension.Logging framework + } + + /// + /// Disable logging whilst running the tests. + /// + /// + /// Remember, if a regression test throws an exception before completing this will not be invoked if it's at + /// the end of the test. + /// TODO: Always invoke this after every test - probably need to make all test cases inherit from a common + /// TestCase class where this can be done. + /// + public static void DisableLogging() + { + // TODO: disable logging while testing in the Microsoft.Extension.Logging framework + } + + /// + /// Parse a UUID stem into a full UUID. + /// + /// + /// The fragment will come at the start of the UUID. The rest will be 0s + /// + /// + /// + /// A UUID fragment that will be parsed into a full UUID. Therefore, it can only contain + /// cahracters which are valid in a UUID, except for "-" which is currently only allowed if a full UUID is + /// given as the 'fragment'. + /// + public static UUID ParseStem(string stem) + { + string rawUuid = stem.PadRight(32, '0'); + + return UUID.Parse(rawUuid); + } + + /// + /// Parse tail section into full UUID. + /// + /// + /// + public static UUID ParseTail(int tail) + { + return new UUID(string.Format("00000000-0000-0000-0000-{0:X12}", tail)); + } + + /// + /// Parse a UUID tail section into a full UUID. + /// + /// + /// The fragment will come at the end of the UUID. The rest will be 0s + /// + /// + /// + /// A UUID fragment that will be parsed into a full UUID. Therefore, it can only contain + /// cahracters which are valid in a UUID, except for "-" which is currently only allowed if a full UUID is + /// given as the 'fragment'. + /// + public static UUID ParseTail(string stem) + { + string rawUuid = stem.PadLeft(32, '0'); + + return UUID.Parse(rawUuid); + } + } +} diff --git a/Tests/OpenSim.Tests.Common/TestsAssetCache.cs b/Tests/OpenSim.Tests.Common/TestsAssetCache.cs new file mode 100644 index 00000000000..b6c7fed6f52 --- /dev/null +++ b/Tests/OpenSim.Tests.Common/TestsAssetCache.cs @@ -0,0 +1,142 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.Runtime.Caching; +using Nini.Config; + +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Tests.Common +{ + public class TestsAssetCache : ISharedRegionModule, IAssetCache + { + private bool m_Enabled; + public MemoryCache m_Cache; + + public string Name + { + get { return "TestsAssetCache"; } + } + + public Type ReplaceableInterface + { + get { return null; } + } + + public void Initialise(IConfigSource source) + { + m_Cache = MemoryCache.Default; + m_Enabled = true; + } + + public void PostInitialise() + { + } + + public void Close() + { + } + + public void AddRegion(Scene scene) + { + if (m_Enabled) + scene.RegisterModuleInterface(this); + } + + public void RemoveRegion(Scene scene) + { + } + + public void RegionLoaded(Scene scene) + { + } + + //////////////////////////////////////////////////////////// + // IAssetCache + // + public bool Check(string id) + { + // XXX This is probably not an efficient implementation. + AssetBase asset; + if (!Get(id, out asset)) + return false; + return asset != null; + } + + public void Cache(AssetBase asset, bool replace = true) + { + if (asset != null) + { + //CacheItemPolicy policy = new CacheItemPolicy(); + //m_Cache.Set(asset.ID, asset, policy); + } + } + + public void CacheNegative(string id) + { + // We don't do negative caching + } + + public bool Get(string id, out AssetBase asset) + { + //asset = (AssetBase)m_Cache.Get(id); + asset = null; + return true; + } + + public bool GetFromMemory(string id, out AssetBase asset) + { + //asset = (AssetBase)m_Cache.Get(id); + asset = null; + return true; + } + + public AssetBase GetCached(string id) + { + //return (AssetBase)m_Cache.Get(id); + return null; + } + + public void Expire(string id) + { + //m_Cache.Remove(id); + } + + public void Clear() + { + } + + /* + public bool UpdateContent(string id, byte[] data) + { + return false; + } + */ + } +} diff --git a/Tests/OpenSim.Tests.Common/UnitTest1.cs b/Tests/OpenSim.Tests.Common/UnitTest1.cs new file mode 100644 index 00000000000..88199a09afb --- /dev/null +++ b/Tests/OpenSim.Tests.Common/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace OpenSim.Tests.Common; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/Tests/OpenSim.Tests/Clients/Grid/GridClient.cs b/Tests/OpenSim.Tests/Clients/Grid/GridClient.cs new file mode 100644 index 00000000000..375512c95f4 --- /dev/null +++ b/Tests/OpenSim.Tests/Clients/Grid/GridClient.cs @@ -0,0 +1,193 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using Microsoft.Extensions.Logging; + +using OpenSim.Framework; +using GridRegion = OpenSim.Services.Interfaces.GridRegion; + +using OpenMetaverse; + +namespace OpenSim.Tests.Clients.GridClient +{ + public class GridClient + { + private static ILogger m_logger; + + public static void Main(string[] args) + { + m_logger ??= LoggerFactory.Create(builder => builder.AddConsole()).CreateLogger(); + // TODO: how to format output: consoleAppender.Layout = new PatternLayout("%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"); + + string serverURI = "http://127.0.0.1:8001"; + GridServicesConnector m_Connector = new GridServicesConnector(serverURI); + + GridRegion r1 = CreateRegion("Test Region 1", 1000, 1000); + GridRegion r2 = CreateRegion("Test Region 2", 1001, 1000); + GridRegion r3 = CreateRegion("Test Region 3", 1005, 1000); + + Console.WriteLine("[GRID CLIENT]: *** Registering region 1"); + string msg = m_Connector.RegisterRegion(UUID.Zero, r1); + if (msg == String.Empty) + Console.WriteLine("[GRID CLIENT]: Successfully registered region 1"); + else + Console.WriteLine("[GRID CLIENT]: region 1 failed to register"); + + Console.WriteLine("[GRID CLIENT]: *** Registering region 2"); + msg = m_Connector.RegisterRegion(UUID.Zero, r2); + if (msg == String.Empty) + Console.WriteLine("[GRID CLIENT]: Successfully registered region 2"); + else + Console.WriteLine("[GRID CLIENT]: region 2 failed to register"); + + Console.WriteLine("[GRID CLIENT]: *** Registering region 3"); + msg = m_Connector.RegisterRegion(UUID.Zero, r3); + if (msg == String.Empty) + Console.WriteLine("[GRID CLIENT]: Successfully registered region 3"); + else + Console.WriteLine("[GRID CLIENT]: region 3 failed to register"); + + + bool success; + Console.WriteLine("[GRID CLIENT]: *** Deregistering region 3"); + success = m_Connector.DeregisterRegion(r3.RegionID); + if (success) + Console.WriteLine("[GRID CLIENT]: Successfully deregistered region 3"); + else + Console.WriteLine("[GRID CLIENT]: region 3 failed to deregister"); + Console.WriteLine("[GRID CLIENT]: *** Registering region 3 again"); + msg = m_Connector.RegisterRegion(UUID.Zero, r3); + if (msg == String.Empty) + Console.WriteLine("[GRID CLIENT]: Successfully registered region 3"); + else + Console.WriteLine("[GRID CLIENT]: region 3 failed to register"); + + Console.WriteLine("[GRID CLIENT]: *** GetNeighbours of region 1"); + List regions = m_Connector.GetNeighbours(UUID.Zero, r1.RegionID); + if (regions == null) + Console.WriteLine("[GRID CLIENT]: GetNeighbours of region 1 failed"); + else if (regions.Count > 0) + { + if (regions.Count != 1) + Console.WriteLine("[GRID CLIENT]: GetNeighbours of region 1 returned more neighbours than expected: " + regions.Count); + else + Console.WriteLine("[GRID CLIENT]: GetNeighbours of region 1 returned the right neighbour " + regions[0].RegionName); + } + else + Console.WriteLine("[GRID CLIENT]: GetNeighbours of region 1 returned 0 neighbours"); + + + Console.WriteLine("[GRID CLIENT]: *** GetRegionByUUID of region 2 (this should succeed)"); + GridRegion region = m_Connector.GetRegionByUUID(UUID.Zero, r2.RegionID); + if (region == null) + Console.WriteLine("[GRID CLIENT]: GetRegionByUUID returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionByUUID returned region " + region.RegionName); + + Console.WriteLine("[GRID CLIENT]: *** GetRegionByUUID of non-existent region (this should fail)"); + region = m_Connector.GetRegionByUUID(UUID.Zero, UUID.Random()); + if (region == null) + Console.WriteLine("[GRID CLIENT]: GetRegionByUUID returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionByUUID returned region " + region.RegionName); + + Console.WriteLine("[GRID CLIENT]: *** GetRegionByName of region 3 (this should succeed)"); + region = m_Connector.GetRegionByName(UUID.Zero, r3.RegionName); + if (region == null) + Console.WriteLine("[GRID CLIENT]: GetRegionByName returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionByName returned region " + region.RegionName); + + Console.WriteLine("[GRID CLIENT]: *** GetRegionByName of non-existent region (this should fail)"); + region = m_Connector.GetRegionByName(UUID.Zero, "Foo"); + if (region == null) + Console.WriteLine("[GRID CLIENT]: GetRegionByName returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionByName returned region " + region.RegionName); + + Console.WriteLine("[GRID CLIENT]: *** GetRegionsByName (this should return 3 regions)"); + regions = m_Connector.GetRegionsByName(UUID.Zero, "Test", 10); + if (regions == null) + Console.WriteLine("[GRID CLIENT]: GetRegionsByName returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionsByName returned " + regions.Count + " regions"); + + Console.WriteLine("[GRID CLIENT]: *** GetRegionRange (this should return 2 regions)"); + regions = m_Connector.GetRegionRange(UUID.Zero, + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(1002), + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(1002) ); + if (regions == null) + Console.WriteLine("[GRID CLIENT]: GetRegionRange returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionRange returned " + regions.Count + " regions"); + Console.WriteLine("[GRID CLIENT]: *** GetRegionRange (this should return 0 regions)"); + regions = m_Connector.GetRegionRange(UUID.Zero, + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(950), + (int)Util.RegionToWorldLoc(900), (int)Util.RegionToWorldLoc(950) ); + if (regions == null) + Console.WriteLine("[GRID CLIENT]: GetRegionRange returned null"); + else + Console.WriteLine("[GRID CLIENT]: GetRegionRange returned " + regions.Count + " regions"); + + Console.Write("Proceed to deregister? Press enter..."); + Console.ReadLine(); + + // Deregister them all + Console.WriteLine("[GRID CLIENT]: *** Deregistering region 1"); + success = m_Connector.DeregisterRegion(r1.RegionID); + if (success) + Console.WriteLine("[GRID CLIENT]: Successfully deregistered region 1"); + else + Console.WriteLine("[GRID CLIENT]: region 1 failed to deregister"); + Console.WriteLine("[GRID CLIENT]: *** Deregistering region 2"); + success = m_Connector.DeregisterRegion(r2.RegionID); + if (success) + Console.WriteLine("[GRID CLIENT]: Successfully deregistered region 2"); + else + Console.WriteLine("[GRID CLIENT]: region 2 failed to deregister"); + Console.WriteLine("[GRID CLIENT]: *** Deregistering region 3"); + success = m_Connector.DeregisterRegion(r3.RegionID); + if (success) + Console.WriteLine("[GRID CLIENT]: Successfully deregistered region 3"); + else + Console.WriteLine("[GRID CLIENT]: region 3 failed to deregister"); + + } + + private static GridRegion CreateRegion(string name, uint xcell, uint ycell) + { + GridRegion region = new GridRegion(xcell, ycell); + region.RegionName = name; + region.RegionID = UUID.Random(); + region.ExternalHostName = "127.0.0.1"; + region.HttpPort = 9000; + region.InternalEndPoint = new System.Net.IPEndPoint(System.Net.IPAddress.Parse("0.0.0.0"), 9000); + + return region; + } + } +} diff --git a/Tests/OpenSim.Tests/ConfigurationLoaderTest.cs b/Tests/OpenSim.Tests/ConfigurationLoaderTest.cs new file mode 100644 index 00000000000..39c29a1f2de --- /dev/null +++ b/Tests/OpenSim.Tests/ConfigurationLoaderTest.cs @@ -0,0 +1,149 @@ +/* + * Copyright (c) Contributors, http://opensimulator.org/ + * See CONTRIBUTORS.TXT for a full list of copyright holders. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * * Neither the name of the OpenSimulator Project nor the + * names of its contributors may be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY + * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY + * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES + * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; + * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +using System.IO; +using NUnit.Framework; +using OpenSim.Framework; +using OpenSim.Tests.Common; + +namespace OpenSim.Tests +{ + [TestFixture] + public class ConfigurationLoaderTests : OpenSimTestCase + { + private const string m_testSubdirectory = "test"; + private string m_basePath; + private string m_workingDirectory; + private IConfigSource m_config; + + /// + /// Set up a test directory. + /// + [SetUp] + public override void SetUp() + { + base.SetUp(); + + m_basePath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); + string path = Path.Combine(m_basePath, m_testSubdirectory); + Directory.CreateDirectory(path); + m_workingDirectory = Directory.GetCurrentDirectory(); + Directory.SetCurrentDirectory(path); + } + + /// + /// Remove the test directory. + /// + [TearDown] + public void TearDown() + { + Directory.SetCurrentDirectory(m_workingDirectory); + Directory.Delete(m_basePath, true); + } + + /// + /// Test the including of ini files with absolute and relative paths. + /// + [Test] + public void IncludeTests() + { + const string mainIniFile = "OpenSimDefaults.ini"; + m_config = new IniConfigSource(); + + // Create ini files in a directory structure + IniConfigSource ini; + IConfig config; + + ini = new IniConfigSource(); + config = ini.AddConfig("IncludeTest"); + config.Set("Include-absolute", "absolute/one/config/setting.ini"); + config.Set("Include-absolute1", "absolute/two/config/setting1.ini"); + config.Set("Include-absolute2", "absolute/two/config/setting2.ini"); + config.Set("Include-relative", "../" + m_testSubdirectory + "/relative/one/config/setting.ini"); + config.Set("Include-relative1", "../" + m_testSubdirectory + "/relative/two/config/setting1.ini"); + config.Set("Include-relative2", "../" + m_testSubdirectory + "/relative/two/config/setting2.ini"); + CreateIni(mainIniFile, ini); + + ini = new IniConfigSource(); + ini.AddConfig("Absolute1").Set("name1", "value1"); + CreateIni("absolute/one/config/setting.ini", ini); + + ini = new IniConfigSource(); + ini.AddConfig("Absolute2").Set("name2", 2.3); + CreateIni("absolute/two/config/setting1.ini", ini); + + ini = new IniConfigSource(); + ini.AddConfig("Absolute2").Set("name3", "value3"); + CreateIni("absolute/two/config/setting2.ini", ini); + + ini = new IniConfigSource(); + ini.AddConfig("Relative1").Set("name4", "value4"); + CreateIni("relative/one/config/setting.ini", ini); + + ini = new IniConfigSource(); + ini.AddConfig("Relative2").Set("name5", true); + CreateIni("relative/two/config/setting1.ini", ini); + + ini = new IniConfigSource(); + ini.AddConfig("Relative2").Set("name6", 6); + CreateIni("relative/two/config/setting2.ini", ini); + + // Prepare call to ConfigurationLoader.LoadConfigSettings() + ConfigurationLoader cl = new ConfigurationLoader(); + IConfigSource argvSource = new IniConfigSource(); + EnvConfigSource envConfigSource = new EnvConfigSource(); + argvSource.AddConfig("Startup").Set("inifile", mainIniFile); + argvSource.AddConfig("Network"); + ConfigSettings configSettings; + NetworkServersInfo networkInfo; + + OpenSimConfigSource source = cl.LoadConfigSettings(argvSource, envConfigSource, + out configSettings, out networkInfo); + + // Remove default config + config = source.Source.Configs["Startup"]; + source.Source.Configs.Remove(config); + config = source.Source.Configs["Network"]; + source.Source.Configs.Remove(config); + + // Finally, we are able to check the result + Assert.AreEqual(m_config.ToString(), source.Source.ToString(), + "Configuration with includes does not contain all settings."); + } + + private void CreateIni(string filepath, IniConfigSource source) + { + string path = Path.GetDirectoryName(filepath); + if (path != string.Empty) + { + Directory.CreateDirectory(path); + } + source.Save(filepath); + m_config.Merge(source); + } + } +} diff --git a/Tests/OpenSim.Tests/GlobalUsings.cs b/Tests/OpenSim.Tests/GlobalUsings.cs new file mode 100644 index 00000000000..8c927eb747a --- /dev/null +++ b/Tests/OpenSim.Tests/GlobalUsings.cs @@ -0,0 +1 @@ +global using Xunit; \ No newline at end of file diff --git a/Tests/OpenSim.Tests/OpenSim.Tests.csproj b/Tests/OpenSim.Tests/OpenSim.Tests.csproj new file mode 100644 index 00000000000..b5de0189a96 --- /dev/null +++ b/Tests/OpenSim.Tests/OpenSim.Tests.csproj @@ -0,0 +1,25 @@ + + + + net8.0 + enable + disable + + false + true + + + + + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + runtime; build; native; contentfiles; analyzers; buildtransitive + all + + + + diff --git a/Tests/OpenSim.Tests/UnitTest1.cs b/Tests/OpenSim.Tests/UnitTest1.cs new file mode 100644 index 00000000000..5b9a2c011d2 --- /dev/null +++ b/Tests/OpenSim.Tests/UnitTest1.cs @@ -0,0 +1,10 @@ +namespace OpenSim.Tests; + +public class UnitTest1 +{ + [Fact] + public void Test1() + { + + } +} \ No newline at end of file diff --git a/bin/Newtonsoft.Json.dll b/bin/Newtonsoft.Json.dll deleted file mode 100644 index ae361956a8c32e90bae1ad86511c74a6b17c69c2..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 698792 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/G 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

(&2~CnJ-;%;?5VMOUS)WKG5lGf`uH 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§uL{z`*q^ybBm&rPCwIX7tz)Q4?q zZqj`~$69kuxoqt{kIVT!>yRsz`T;QIYfB>DUmWUmw%fsY80pj5E`WY2xz9q5e=*0~ z+3I>6>?il3`&7?6%UNE^w2bRlxuL85G7eXfMCC!_%B|^rE&qO4&-E!t90L9LT%h;C zdC40HU}nxSy6+<^SutpVBDtpVk8XVR6c zu>EIpd|4bmy8+EN2Q{Gfmc#tq2GkEt;QnX=_eXi0&IAsZ&*7%9y;B-c`zd5Q3fYcA z4p(U8Bdr|P&td)9Ccfsq=9>6=^qR+ZEadzyY(VXG5TDo1Z9w&;(1i0Mi`kCF%+EF9 ze>rwG$5Y6Za7I_c1HI*jFXM8{tOLZ@ z_$vJ#_w8wpvVB@#<;H0n`21oD+Z&?ahu)gzSH4I7lN@w@$<;qC`@Ma>ddm5Sk6#yf zTkyU9Dl=E}Y&w5#jja1s&cd4U5gGr&8hKvv@c9eb&nf@W^1bxs^Fw{azZZU)zuAok zbv<9{9Ay8Na9m3`t|c6o?dPIkOzS;U)mOKtV-6mF|5o?gT)&-s?lMIEjV7w&|9Q|) zy+ZXbgZp(nZv`h+NPpf7R_XmV{M`uEzYng|c0LNOVSm=JKWo^ZH?;nFp%K;gJTRjA zW<5?G)OGvs^VZgI|Esajy$_n{N9DM_w#Gh}T3cg}FKcV;@nx;H_w^GFnnxa#_q-1t ze|}`_^~gbUHSAv0x7Q&D%`Y@=ZH*jXzBX_S=VNV+JU@$t*4D^#vcL9STVszyYjt}J zp0J@tjxVD@yWei+_%?HVn>oI%v~Q(-EA62^_uzX=1D*@Zdx{rL@2k(LBh&43<5#BJ z_o&{aeivHaYc7HEUh`qvKPEqgeHqR}Ux4yH^Nr0m&&FoNpMdXQpuF$=2aIC9_Qj28P|%by3^=bQ4}SnOk3cI*9=b6Xx$ey8PzN`Ds5Z(BxZT>H*`1@^PReiqpO zjXW>!3>Y$Fa7z9^_5S&CEug7JU<`Q zb<*fPqU+=j<_OnGnfc4~e?tE!^xOB2&YO8d=9{>57edoY^zT zJ_l|zo?1EHJv=j0@ha{^yf|~d9>=GdEftg6+Uw<^wf4F$ zp!R2~Mv_C7{vqQTw}SiUlv;aS=&SDs3~lY}Y{Y$mQJ}n6abByVdKM`9($;XTy?&U> zb`~=KT;|VLeEsjnJl2}m@pQK?QTuXdNv&Pij;^adv&T50624E)?yGw6Y`gu>p6xgj zVVCy`WLy(x_toQIqgm`+jrS#*XD@b6U4r8@*ogHxAM}0|zrO)zgG<4~c#q=i;CRf> zHDCkQ^9|rXaQ`+*-U?1l8gmDE7dQ<0@1MQ9;=7mQeYn~4oriI~_ndoo#W);~7n(OJ z{)+YQxWL;jkAaP_*J3+~{Uopvznrnh-~AP@O%VJ3ibdpTe1CLY^Bs9^vJ?4VSuXzh z&X4hZdLinygD=j`@cJmG^X4@fuEQ9&+kEGK+^_rN?8g}Q82!5|K7n2G97X?+%|2Yw zSI0A7pJzxrPh;In|31Tgf7hJ(&IzYW`yX>$ppNU~3OlZkIj)a6uFop$xIW{!KI6DP ztFYr*;XI9bA2=sgYme_Su9H};{rymt-nVHq4*4|Ce;?~Q89L`Palb3)v~T9%^9OQG-rj=gZrPY;BK^g$sDIr_G>)PtF+^cI={pCTjx~KUPXJ~ z%2RYZ^sS7ZA@O}HuL2v5oo_sOuKYE&!&P%u*4pcuHMREocSz+Bth@i2Geqx0EizGV zNBbTUuCUn77`8K}Qs(Oc^xJOdeDyz|9oL9TyPZZVN9qP;1Y(lPd4T z{pIJ}rRw20F5P}^sanpX$%@Uo{v5Me*IlmP=30AQu$jk;&6b?#c@NlY3Y=G~Pq@(X#0&S(Zht5AN4K>Un%dOSI&!SJNR8^R;|`r++8?JL%s^ z|4#aA>umc2>TLT%+5S+ryWbK2%@-|pi9dBw)Mfh@bxFT){p_;+TOC&vD4W&N$Jzm@g3vVNBSEd5#fv-EGLe>?r#>EBNO zPWpG!zmxu*^zWj77yY~F-$j3c{sR34`U~{0tdr{s`CflN&LSlkaK8f!VsAON{x-;%uFqC$2KtI{O~qcGZ6{J6V4x^R?>r zOxzsgdkh=-p2f!hypOTE%b3XGj5`gkTzsAT;}=9Pc3+G46e5c^x}Sl*3HZZk8+|PnPT)e_v#C7LQi`Vk|^PakfAIN(r zEA)Lq$F+~++^6IGt^Z)1eV?FQx1|B^-7YSxz4EehT?cf(%Z_lqKB=?M?aQoxOquJq zTYr!A+kmIuKEL+V+vnM}^)_#9y?s7Cpx!=@9$IgoKL^N>w2$!Ii{tJ>GrrzFubWbD z?^}+qx6kdu^|Bp4yKHW~Y+tXLTQA?Q`d=Qdm-F~3mw%?;PtLh~q57Yx+RpD619<;v ziP{~rq~6}AiBZQ?d#A3XUa5NE)HT#=RG&6=ZN2=Zk6SLku5uNw_olel)~^R|xqNN? z87Im9xwd}v36^)AY}p6j&sU?K`~{{lgVxqp!+#&*uf;mL3EG2tzl2`4*2dd$t*y8B zb=KC~`#Nju?>m6^P%lrhKPm0c_or@Pe>Sim8`%DadfV;>wzq-pZD2baG>_M8U^^R_ ze|(KS&W*3J$GPz}_Bc1b#vbRw^oQvW(;udPv*vxKY7g(DEn)l;#xG&~62^bV>zXx; zTf?|Dj9YU&Fa4Y8-%S5z`nS@*wcZ}*v-S2kzrEfb$G6wp^UF^9chO&6otDZ1`CH{ez8ZT6(Ph!=bW{j_G## z?x17bKF8{pVY>&M#e8pZzIzJx_mllYd407~-`{QYdK&EYtEa(U?`3&kx|a4@+UK$# zbGc6D>iU=(8Nj%?yq`5!`>`Z4m+PgE8DQ`u0W;VPMd{Pe0sTBuKabYWy-PB$RZBw4l$N)mbeTI`XGZJIX4j~w z+YEN!h{&Ptn^F4Qx1e0;mOd087tIb-hsLAl($0p&{fMwHjOe}Zz2`(BjS zyVEEW?oO1CYKEPf;VI3qOEcs&LqRjVtQq!bhSxR2KF!eMeh?WBx*tJV)NEz|w#?)?JgKCkD>KBmW8h4P@c9%a$nA7$A)8081vlTjY=4nz5o_cWBBct@iA z)H?>{G4Di_rs6!5o{AvKJ{2t}Yb$1>99khW=Btny3slIAon9d`Hbw10wTIO{3-$`L zvtr(r^=4Pa#i*OB_#w(d#T_VLuDBEBo{D>}tTwM#+<&FpG*&)<$N`lPUD*%cg9c$= zi&Z`f%ks*XP_C>jqP({9A1K#UUffo1uCIIq<=V>HSiMP9E<>5B+=cSi%0bKO&4$XA zDDSB3M!C6i=-2Db-Id=(xwZ1QDAScAzEN+omA9gNpz=eM+bd@*uQxj@J5fGWS-S## zuDlv$uJTQkh061+{@I{mdnO zWdDk4d7YLwXqnV>;$RmnOGRLMF#y-HTp z$f|phe+6pwGtX-Io|cES^jC|0l$JMZd7qXKYx%5}Z=kF<`>OL8MNjq1C=XV@fu6so z_G0xxSjyFhQGQVUDas?&gT7gBKB_+BoBhnkn!%}&R{Cn`*K&lGleBEra%+h7@7`)ia#YhOX>t9=(?vNe0Bc=i(f5)IyoYpUMI(yk#%x37+qIFAI7M6 zeBB@Lc`80*L~04^=NA1u3z_?vxyaeiT%eXkS}sw0Og}Hz&nvavq4m-qU%ehN>h*|G zFKc^by{zrg^|H3d*UQ>Iw_euvlzQn~uwIUq;d(h*&Z?KA<=lEXT3%EyN6UrvahHHEQi?cm_l(-na9=g6#DEjyU<&#F16$u?}eq%cpog?YI(Wweth1opZ7Fo z;C;QZ1C}?`vahidmY&96pgh>P4P~+MA(Z9Dhf#je_z21)jgO)HsBs6%PZ}Rb`Dx>m z*WjwMugu2vePv%;+gJ9rL|@t0QhjA#yS1e77dncO9o4}$Y9A98!XwD50-2z2TQhV2TQg!gC*PbgC*PA!LsIG)w-__ zkqrBWNQRyvlHuTxQ*kZ*?Gxo%I&tFDSRttsqu1iBed1TI!&&>pW#7VC`^1}9V-=kw zb<0|QaFS#=a*|~D=p^aGlA+>_3>AB9sMr_!#j?mRmL-0%MEnvN^Gk1+`=z%l{nFcO z{nFbte%Vjo7$a-xJuPn-E1%bCS<>>))-qn~8@2q~1o`Yt)LL4ee2#n`q2(AYr=oP3 z(D^^O1~*~x7dzL)@$dnEp)=ld7FdJtGQ$4mi6?|7ndgDuAP3N z_}8g@O?(sm9rO#uzk~KJaECdxe=l{f>M!>HiTWssTQb@YJnTgLkaxY{Fmq!ZH&L~p zc*#qNmj&TJ`(3 zUiy2%XE4sA)cswye@);pvo1bDiGI=EqIzR|0d*TF`IphYn)Y={%qw*VxWlx(+e!O2 zQ2Mch{x0<|eYZe+x7ydld%-iX9)!|=q2$5ebY1Wg538-VVw-*ra+@ zJPpda?f`XO)V?OZ4b*v2y(+$w%z+Z$1?u)x`&ub{+xLFs>g<*K-k{s1WTo9G`$e~|uWaEE#L-7u*0q<*YlCFTi~ zel4SYHSG!7+d&zZP}*NdZc<{tK%Fm8)U!cTysU7o0 z?p3aemq48_P}=XM{}3qoj;dZ2cf2;v4eC6B;_na2@ybvCa8U9HWgJbQ#EnzEDjou* zy=GA2gc8?6E>L2=K#9YJ2PlylE0hWOaCG2qtxyS{54AR`fFZL;`=LC#fO7B541OtQ?Fn%2n}Y$~Ez%`r|!9$-j>F40Q)6`8#Rfrd$=z)89>dFDU&L%6tiHq`#nS z7dO7&i(e>yFDUjZ)tFcMh2n1_N6bZw7UK)o!)4*FxP2er^sOSfCo~4wQP! z=x+z5zEIj*N3K_5{eaTHZPfBBC#l~BN}>n0C@5`P%{+UCrXDE$#ioba1Y>D^B1F8YPy?eWBC~C|AXssK-%Q(U;+80nqz%KZs zpdNQY$LUHUpyU%uzC+|u<*K-;wfz%HKKy}JZO;oze19d*|DcRRC~*PW zo0Mzf>IO=oc;Pp{#m&{1EL&m6)eGTh9$j`$B2oOICq$9P6(d z^8`ve!$E0BDDlF6w99d)iS}_y%o`~6LZGalW>D%2rM^)5*+LxwB|ZvDd>bh7LWvhj zd`va2&uLG9QZETgy>?LQ38kJ;`nisJlM>qjl<{Xksn?+z>s9@0;+^C+@N=`~;11PT zub{M-rxr^6E>PMPO1nboZ#VT`C9cmvi9ba9QQDn)YxjV1{pD4ziC2MgKI>1r|4WoS z0qQ0t)-QE4sPj#G3)u?FabSUJoNqv#XZmsbU)#q`e;vna>I5kBwT|}nN~~-8)AV;J zF^_7;eoucV^$zMBDEYdSm^b>n$-T-|@k7){soi*+Li#I|dG;tVU)24nhl3I)l(;5x z9R1Do3&r0;E+8Z7$NVU<4ryOTt|k-eUlmWP#_>`0^7uOXH<4+zV}GZ=llE=OiCp6{!p)0;(Q6p{@4ymyinrT zk(OoKX7TN$w!K z=ogB=pj;L2rrt|k1Z6%B(SDS6+$^;71nNA2I!~mJ{^9fs#UD`OdW?D;br6(xnnB4Y zl=v2MfpS$mro?)oeKqav;12WQyX$CAgF5~%QSxQgzdYW_xGwsI;_oJVX%~w95a}5p z8pi=8jsr?;e^A;JN_-R9OuJC*Eo7^5RXnQ1{s7is{6hJ@yiDzD;w$KHr#+<_+nfFj z{T=jY>F=b!i}r5nUh1-HoDXPs<1H}#o(@VseV~qq_9oh!sl%$Vj%jbBeHnEEl>M!p z_LSOj9H+m7_DsJy|IIDbU`n#P=)O4^g}EcChvv)P94qj)v17Q2X+D6a7K@ zo9S<%Jwp33Q0@mNsMo6>*EiH1psp*m<9i|~+pQCn@0~eN+V27-PAJ!N1yHU-x|M6< zMNr~;L5UMe|Ai8Nh}sOYac)rJgc2u|IFD+auW26+N}N#Qn#g9_g<==Vb(~Q0w9wuL zN`0Zk3B?~%jr$$6w}TQVl(={%7pT{9)ZJvS+A$B*WhLf;+B<~f z2Zx#Gdwig@-$Z+mI;0xsX;AjF7W!kLjC&dN3UHWtrKcU#{?oor?N7wl)89$^Hr4T- zE>QbVdpFsucJ!aR49a!$A!_%DwjH6&lNXeAF`Rax*qdk%(jHR#s(2WDMaDyag#IY~ zLYa?cs@KF5^a~G|^}E`sh0@MCGEKYifO&dX2enY*I>{XEU0F=UlDE5L97f1yr9^9)WQSi zre}v!3&r0=hLl*}^b5t`LblQ$0cAb3fzpmp;+Bysl(-HBC9WNmIHAO?Bh&PE&@U8! zCz+$ai+-W_yUE>3e2*o2L1{-Qafe6;Z*R-_!wrgGD1I;Lr++y8Lh%QbIR80i;DE4J!JMBWTuOmBX7mB@;?4n&L_HJ@FDEob{`f)u@ds&I&8TDaM;@tSv zkMz?EN}l1=&14%H10}v)^{RM^x`Te9)bFAeiY|asubW!_tA)=0mnd;%#vNju`3kp# z659ooI4|j=e>nX@@i&ppvydhe64y~`x0Lwxw0F=h6nmFyoL8xPsfFVA z`q&S$8I=9Jg}N1#`G|tLe}Lj&21@+|DD$(9dObMIZ0SkU-bs5mSyE!VQhWV2PXHWd zZadIKJwi2}pHjC{M?vXFO!aLCmeHT2eI0ct*-h>SrC+_E^#2g;ht-aCHO%Jqf-){2 zDC6?0#&MPQCNe~OGk6B#!=UuPh5ji0LY+5q8U5|l>!{Z&@%@bUPTIH8-bH&i?YlwQ zF1@rLqWv)K?%^B{DB~GU-9+6?-9p_)y^MMVDD%}$`#Rb?s5_~hXjF4?)0+jZJ(tbPHL2d(Odv#IortYOaOzl3+)~^EPdTTg!GucMAlWDSp z%#mH>Zcxq>MNsfzJ@>SpRTvYkxP-$9)Pb$w75sC%j1BiVmY`q@mj z(cTWq{w0*}aUHaGffC>_(f_gU;0*-W;P?PLep z1xi1AsoiI@KB&hB>Nc{SOsOC1j5D)psZt|wA)KR{!y*k-Jq@$P~v^G52rmy-Avs^#z48Ro1nj)_6#`8 ztncZd&Vq6us*AdpEYohrvLB%C2V?-0dd<{rWIL$)19ca5FSUCd#{)|J;ndA!8`(~F zkX>XEl=G`l`qN9|A5*AZD0VmL0d<{H2UMfqO5B$KrN8Y;To=*a0ZN{%YCI32?j_w5 zI1W(rQ#X@gwd4AWI!$(vS@q+1N8Lph$zHMyN`5nu<03tv?DxZ|o5={-MkdI1GEH`n zIkJl^fzn4@$ib>MpVf>UA`= zIoE18DBtU=K&dwzl>Rl7t+cmMx04-e$MG7J`!w5B<9>^8KFX>FSd4$prx9S_?K2WbK>G#t=oD9+* zqF<=KxJGmZ|erKpV$Q-E8S*g3o64^^S=gByp?-A-aNFR8>bUizq zI;i@yXPc=bs?T|@4V2^h3MJ-`dOdX-JYc^2Tn8xmbE+SCu8Vp%DA(0RwI6=YH$~z{ zJs(uAem%PtWI+r{;>vww#$N@tK@Y`Ccxl9Gs6Rm*?9+oiDP3>>`Wc0h7&} zX~IK!pEB@5Pp-FS!}Pb35%7TdP9X|PoKWIc(4SEM6NRMuaUYWY44G5^-wTI9Y1afL z-^6a8a!q$c`CNBe`9*h8Ipf8{pv3z^694@dBg*a<(@OVC8Bo?u5!CY{DE%s{{k8*U zy0r_%?tsJ0J3T(yM}RsH)M54Cc3=hgxv6-6J^gu5>hGp5QI|nkH%_zl`^f+(_7HWH zOoGy$Q1X?)VdhZJVcMOrwFf~NPY9Ixj!;LbSAcRpOwgVLC1094L%j`@xV#d_E!qp9 z^shv_Q0J$`(gTX$2TDKuv&e4pX12}04V3sib%}K5Sihef z0ZML@7vN|G7+h1y^0JoRqX%i|?b=0zxR&RmWIl=G^eIz&dv1c=4{Qj&Tb zDE;3}mO#mSnA%(*@i)BWfMWN9(%uMA>IK#R_)AgxlVk?e{*xuOZ|M<={V@2sx%GYj zg_8GZPXxsM<(HD6W{x1rB0F=Q07S} zaXIjSdEsT}B0EkWc))me`>7+UXYNjd8n62H-DObv8<@|2gW6xRL^>B+yAM2IUf=Df z4v|qZNoL4ASp*N5e!nSEI}2<+KN%t;-~lu5H&N;&nIZF_Y)7H=vqUWv?JTr@p=iJA zS9*k^Lu8apk{Qyu#Lj~cJYY8b#t+IoM`=%z8Pd7b#{0UoJeN~USgk-nvzZ!#F+_9uN&nCDQr2 z&EqFSWRy&j88T0nNaq{2ev-_Pd9naX|4P)(a<)f?$S4_GVdK-_0aLjrOC7$-+EZki z%zTsiNwZSwoxjHcbzaC2nFQr~Rf$@t`_a`l-T_4i!2>3~C!&0OPeS>RJxNfHBdctD zlFX2KvP4F&WgM9XWxvi)=cp6calMf_vPk;AW&Oe9Ty6UqAroYp%!4xi(6?<|l1ziL zKV+zL)OqUYcWitCl=_kDZF~}xdC7xfkKJIICktfmMjKxwOQ6J?o2)$u%J@T|*rQ~O zOwjLJYvcT�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 diff --git a/bin/Newtonsoft.Json.xml b/bin/Newtonsoft.Json.xml deleted file mode 100755 index 6ca71905873..00000000000 --- a/bin/Newtonsoft.Json.xml +++ /dev/null @@ -1,11193 +0,0 @@ - - - - Newtonsoft.Json - - - - - Represents a BSON Oid (object id). - - - - - Gets or sets the value of the Oid. - - The value of the Oid. - - - - Initializes a new instance of the class. - - The Oid value. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized BSON data. - - - - - Gets or sets a value indicating whether binary data reading should be compatible with incorrect Json.NET 3.5 written binary. - - - true if binary data reading will be compatible with incorrect Json.NET 3.5 written binary; otherwise, false. - - - - - Gets or sets a value indicating whether the root object will be read as a JSON array. - - - true if the root object will be read as a JSON array; otherwise, false. - - - - - Gets or sets the used when reading values from BSON. - - The used when reading values from BSON. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - if set to true the root object will be read as a JSON array. - The used when reading values from BSON. - - - - Initializes a new instance of the class. - - The containing the BSON data to read. - if set to true the root object will be read as a JSON array. - The used when reading values from BSON. - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating BSON data. - - - - - Gets or sets the used when writing values to BSON. - When set to no conversion will occur. - - The used when writing values to BSON. - - - - Initializes a new instance of the class. - - The to write to. - - - - Initializes a new instance of the class. - - The to write to. - - - - Flushes whatever is in the buffer to the underlying and also flushes the underlying stream. - - - - - Writes the end. - - The token. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - - - - Writes the beginning of a JSON array. - - - - - Writes the beginning of a JSON object. - - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Closes this writer. - If is set to true, the underlying is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value that represents a BSON object id. - - The Object ID value to write. - - - - Writes a BSON regex. - - The regex pattern. - The regex options. - - - - Specifies how constructors are used when initializing objects during deserialization by the . - - - - - First attempt to use the public default constructor, then fall back to a single parameterized constructor, then to the non-public default constructor. - - - - - Json.NET will use a non-public default constructor before falling back to a parameterized constructor. - - - - - Converts a binary value to and from a base 64 string value. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from JSON and BSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Creates a custom object. - - The object type to convert. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Creates an object which will then be populated by the serializer. - - Type of the object. - The created object. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can write JSON. - - - true if this can write JSON; otherwise, false. - - - - - Converts a to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified value type. - - Type of the value. - - true if this instance can convert the specified value type; otherwise, false. - - - - - Converts a to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified value type. - - Type of the value. - - true if this instance can convert the specified value type; otherwise, false. - - - - - Provides a base class for converting a to and from JSON. - - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a F# discriminated union type to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an Entity Framework to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can write JSON. - - - true if this can write JSON; otherwise, false. - - - - - Converts a to and from the ISO 8601 date format (e.g. "2008-04-12T12:53Z"). - - - - - Gets or sets the date time styles used when converting a date to and from JSON. - - The date time styles used when converting a date to and from JSON. - - - - Gets or sets the date time format used when converting a date to and from JSON. - - The date time format used when converting a date to and from JSON. - - - - Gets or sets the culture used when converting a date to and from JSON. - - The culture used when converting a date to and from JSON. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Converts a to and from a JavaScript Date constructor (e.g. new Date(52231943)). - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Converts a to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from JSON and BSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts an to and from its name string value. - - - - - Gets or sets a value indicating whether the written enum text should be camel case. - The default value is false. - - true if the written enum text will be camel case; otherwise, false. - - - - Gets or sets the naming strategy used to resolve how enum text is written. - - The naming strategy used to resolve how enum text is written. - - - - Gets or sets a value indicating whether integer values are allowed when serializing and deserializing. - The default value is true. - - true if integers are allowed when serializing and deserializing; otherwise, false. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class. - - true if the written enum text will be camel case; otherwise, false. - - - - Initializes a new instance of the class. - - The naming strategy used to resolve how enum text is written. - true if integers are allowed when serializing and deserializing; otherwise, false. - - - - Initializes a new instance of the class. - - The of the used to write enum text. - - - - Initializes a new instance of the class. - - The of the used to write enum text. - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - - Initializes a new instance of the class. - - The of the used to write enum text. - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - true if integers are allowed when serializing and deserializing; otherwise, false. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts a to and from Unix epoch time - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Converts a to and from a string (e.g. "1.2.3.4"). - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing property value of the JSON that is being converted. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Converts XML to and from JSON. - - - - - Gets or sets the name of the root element to insert when deserializing to XML if the JSON structure has produced multiple root elements. - - The name of the deserialized root element. - - - - Gets or sets a value to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - true if the array attribute is written to the XML; otherwise, false. - - - - Gets or sets a value indicating whether to write the root JSON object. - - true if the JSON root object is omitted; otherwise, false. - - - - Gets or sets a value indicating whether to encode special characters when converting JSON to XML. - If true, special characters like ':', '@', '?', '#' and '$' in JSON property names aren't used to specify - XML namespaces, attributes or processing directives. Instead special characters are encoded and written - as part of the XML element name. - - true if special characters are encoded; otherwise, false. - - - - Writes the JSON representation of the object. - - The to write to. - The calling serializer. - The value. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Checks if the is a namespace attribute. - - Attribute name to test. - The attribute name prefix if it has one, otherwise an empty string. - true if attribute name is for a namespace attribute, otherwise false. - - - - Determines whether this instance can convert the specified value type. - - Type of the value. - - true if this instance can convert the specified value type; otherwise, false. - - - - - Specifies how dates are formatted when writing JSON text. - - - - - Dates are written in the ISO 8601 format, e.g. "2012-03-21T05:40Z". - - - - - Dates are written in the Microsoft JSON format, e.g. "\/Date(1198908717056)\/". - - - - - Specifies how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON text. - - - - - Date formatted strings are not parsed to a date type and are read as strings. - - - - - Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . - - - - - Date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed to . - - - - - Specifies how to treat the time value when converting between string and . - - - - - Treat as local time. If the object represents a Coordinated Universal Time (UTC), it is converted to the local time. - - - - - Treat as a UTC. If the object represents a local time, it is converted to a UTC. - - - - - Treat as a local time if a is being converted to a string. - If a string is being converted to , convert to a local time if a time zone is specified. - - - - - Time zone information should be preserved when converting. - - - - - The default JSON name table implementation. - - - - - Initializes a new instance of the class. - - - - - Gets a string containing the same characters as the specified range of characters in the given array. - - The character array containing the name to find. - The zero-based index into the array specifying the first character of the name. - The number of characters in the name. - A string containing the same characters as the specified range of characters in the given array. - - - - Adds the specified string into name table. - - The string to add. - This method is not thread-safe. - The resolved string. - - - - Specifies default value handling options for the . - - - - - - - - - Include members where the member value is the same as the member's default value when serializing objects. - Included members are written to JSON. Has no effect when deserializing. - - - - - Ignore members where the member value is the same as the member's default value when serializing objects - so that it is not written to JSON. - This option will ignore all default values (e.g. null for objects and nullable types; 0 for integers, - decimals and floating point numbers; and false for booleans). The default value ignored can be changed by - placing the on the property. - - - - - Members with a default value but no JSON will be set to their default value when deserializing. - - - - - Ignore members where the member value is the same as the member's default value when serializing objects - and set members to their default value when deserializing. - - - - - Specifies float format handling options when writing special floating point numbers, e.g. , - and with . - - - - - Write special floating point values as strings in JSON, e.g. "NaN", "Infinity", "-Infinity". - - - - - Write special floating point values as symbols in JSON, e.g. NaN, Infinity, -Infinity. - Note that this will produce non-valid JSON. - - - - - Write special floating point values as the property's default value in JSON, e.g. 0.0 for a property, null for a of property. - - - - - Specifies how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - - - - - Floating point numbers are parsed to . - - - - - Floating point numbers are parsed to . - - - - - Specifies formatting options for the . - - - - - No special formatting is applied. This is the default. - - - - - Causes child objects to be indented according to the and settings. - - - - - Provides an interface for using pooled arrays. - - The array type content. - - - - Rent an array from the pool. This array must be returned when it is no longer needed. - - The minimum required length of the array. The returned array may be longer. - The rented array from the pool. This array must be returned when it is no longer needed. - - - - Return an array to the pool. - - The array that is being returned. - - - - Provides an interface to enable a class to return line and position information. - - - - - Gets a value indicating whether the class can return line information. - - - true if and can be provided; otherwise, false. - - - - - Gets the current line number. - - The current line number or 0 if no line information is available (for example, when returns false). - - - - Gets the current line position. - - The current line position or 0 if no line information is available (for example, when returns false). - - - - Instructs the how to serialize the collection. - - - - - Gets or sets a value indicating whether null items are allowed in the collection. - - true if null items are allowed in the collection; otherwise, false. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with a flag indicating whether the array can contain null items. - - A flag indicating whether the array can contain null items. - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Instructs the to use the specified constructor when deserializing that object. - - - - - Instructs the how to serialize the object. - - - - - Gets or sets the id. - - The id. - - - - Gets or sets the title. - - The title. - - - - Gets or sets the description. - - The description. - - - - Gets or sets the collection's items converter. - - The collection's items converter. - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonContainer(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonContainer(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets a value that indicates whether to preserve object references. - - - true to keep object reference; otherwise, false. The default is false. - - - - - Gets or sets a value that indicates whether to preserve collection's items references. - - - true to keep collection's items object references; otherwise, false. The default is false. - - - - - Gets or sets the reference loop handling used when serializing the collection's items. - - The reference loop handling. - - - - Gets or sets the type name handling used when serializing the collection's items. - - The type name handling. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Provides methods for converting between .NET types and JSON types. - - - - - - - - Gets or sets a function that creates default . - Default settings are automatically used by serialization methods on , - and and on . - To serialize without using any default settings create a with - . - - - - - Represents JavaScript's boolean value true as a string. This field is read-only. - - - - - Represents JavaScript's boolean value false as a string. This field is read-only. - - - - - Represents JavaScript's null as a string. This field is read-only. - - - - - Represents JavaScript's undefined as a string. This field is read-only. - - - - - Represents JavaScript's positive infinity as a string. This field is read-only. - - - - - Represents JavaScript's negative infinity as a string. This field is read-only. - - - - - Represents JavaScript's NaN as a string. This field is read-only. - - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation using the specified. - - The value to convert. - The format the date will be converted to. - The time zone handling when the date is converted to a string. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation using the specified. - - The value to convert. - The format the date will be converted to. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - The string delimiter character. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - The string delimiter character. - The string escape handling. - A JSON string representation of the . - - - - Converts the to its JSON string representation. - - The value to convert. - A JSON string representation of the . - - - - Serializes the specified object to a JSON string. - - The object to serialize. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using formatting. - - The object to serialize. - Indicates how the output should be formatted. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a collection of . - - The object to serialize. - A collection of converters used while serializing. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using formatting and a collection of . - - The object to serialize. - Indicates how the output should be formatted. - A collection of converters used while serializing. - A JSON string representation of the object. - - - - Serializes the specified object to a JSON string using . - - The object to serialize. - The used to serialize the object. - If this is null, default serialization settings will be used. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a type, formatting and . - - The object to serialize. - The used to serialize the object. - If this is null, default serialization settings will be used. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using formatting and . - - The object to serialize. - Indicates how the output should be formatted. - The used to serialize the object. - If this is null, default serialization settings will be used. - - A JSON string representation of the object. - - - - - Serializes the specified object to a JSON string using a type, formatting and . - - The object to serialize. - Indicates how the output should be formatted. - The used to serialize the object. - If this is null, default serialization settings will be used. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - A JSON string representation of the object. - - - - - Deserializes the JSON to a .NET object. - - The JSON to deserialize. - The deserialized object from the JSON string. - - - - Deserializes the JSON to a .NET object using . - - The JSON to deserialize. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type. - - The JSON to deserialize. - The of object being deserialized. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type. - - The type of the object to deserialize to. - The JSON to deserialize. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the given anonymous type. - - - The anonymous type to deserialize to. This can't be specified - traditionally and must be inferred from the anonymous type passed - as a parameter. - - The JSON to deserialize. - The anonymous type object. - The deserialized anonymous type from the JSON string. - - - - Deserializes the JSON to the given anonymous type using . - - - The anonymous type to deserialize to. This can't be specified - traditionally and must be inferred from the anonymous type passed - as a parameter. - - The JSON to deserialize. - The anonymous type object. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized anonymous type from the JSON string. - - - - Deserializes the JSON to the specified .NET type using a collection of . - - The type of the object to deserialize to. - The JSON to deserialize. - Converters to use while deserializing. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using . - - The type of the object to deserialize to. - The object to deserialize. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using a collection of . - - The JSON to deserialize. - The type of the object to deserialize. - Converters to use while deserializing. - The deserialized object from the JSON string. - - - - Deserializes the JSON to the specified .NET type using . - - The JSON to deserialize. - The type of the object to deserialize to. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - The deserialized object from the JSON string. - - - - Populates the object with values from the JSON string. - - The JSON to populate values from. - The target object to populate values onto. - - - - Populates the object with values from the JSON string using . - - The JSON to populate values from. - The target object to populate values onto. - - The used to deserialize the object. - If this is null, default serialization settings will be used. - - - - - Serializes the to a JSON string. - - The node to serialize. - A JSON string of the . - - - - Serializes the to a JSON string using formatting. - - The node to serialize. - Indicates how the output should be formatted. - A JSON string of the . - - - - Serializes the to a JSON string using formatting and omits the root object if is true. - - The node to serialize. - Indicates how the output should be formatted. - Omits writing the root object. - A JSON string of the . - - - - Deserializes the from a JSON string. - - The JSON string. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by . - - The JSON string. - The name of the root element to append when deserializing. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by - and writes a Json.NET array attribute for collections. - - The JSON string. - The name of the root element to append when deserializing. - - A value to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by , - writes a Json.NET array attribute for collections, and encodes special characters. - - The JSON string. - The name of the root element to append when deserializing. - - A value to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - - A value to indicate whether to encode special characters when converting JSON to XML. - If true, special characters like ':', '@', '?', '#' and '$' in JSON property names aren't used to specify - XML namespaces, attributes or processing directives. Instead special characters are encoded and written - as part of the XML element name. - - The deserialized . - - - - Serializes the to a JSON string. - - The node to convert to JSON. - A JSON string of the . - - - - Serializes the to a JSON string using formatting. - - The node to convert to JSON. - Indicates how the output should be formatted. - A JSON string of the . - - - - Serializes the to a JSON string using formatting and omits the root object if is true. - - The node to serialize. - Indicates how the output should be formatted. - Omits writing the root object. - A JSON string of the . - - - - Deserializes the from a JSON string. - - The JSON string. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by . - - The JSON string. - The name of the root element to append when deserializing. - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by - and writes a Json.NET array attribute for collections. - - The JSON string. - The name of the root element to append when deserializing. - - A value to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - The deserialized . - - - - Deserializes the from a JSON string nested in a root element specified by , - writes a Json.NET array attribute for collections, and encodes special characters. - - The JSON string. - The name of the root element to append when deserializing. - - A value to indicate whether to write the Json.NET array attribute. - This attribute helps preserve arrays when converting the written XML back to JSON. - - - A value to indicate whether to encode special characters when converting JSON to XML. - If true, special characters like ':', '@', '?', '#' and '$' in JSON property names aren't used to specify - XML namespaces, attributes or processing directives. Instead special characters are encoded and written - as part of the XML element name. - - The deserialized . - - - - Converts an object to and from JSON. - - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Gets a value indicating whether this can read JSON. - - true if this can read JSON; otherwise, false. - - - - Gets a value indicating whether this can write JSON. - - true if this can write JSON; otherwise, false. - - - - Converts an object to and from JSON. - - The object type to convert. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Writes the JSON representation of the object. - - The to write to. - The value. - The calling serializer. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. - The calling serializer. - The object value. - - - - Reads the JSON representation of the object. - - The to read from. - Type of the object. - The existing value of object being read. If there is no existing value then null will be used. - The existing value has a value. - The calling serializer. - The object value. - - - - Determines whether this instance can convert the specified object type. - - Type of the object. - - true if this instance can convert the specified object type; otherwise, false. - - - - - Instructs the to use the specified when serializing the member or class. - - - - - Gets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - - - - - Initializes a new instance of the class. - - Type of the . - - - - Initializes a new instance of the class. - - Type of the . - Parameter list to use when constructing the . Can be null. - - - - Represents a collection of . - - - - - Instructs the how to serialize the collection. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - The exception thrown when an error occurs during JSON serialization or deserialization. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class. - - The that holds the serialized object data about the exception being thrown. - The that contains contextual information about the source or destination. - The parameter is null. - The class name is null or is zero (0). - - - - Instructs the to deserialize properties with no matching class member into the specified collection - and write values during serialization. - - - - - Gets or sets a value that indicates whether to write extension data when serializing the object. - - - true to write extension data when serializing the object; otherwise, false. The default is true. - - - - - Gets or sets a value that indicates whether to read extension data when deserializing the object. - - - true to read extension data when deserializing the object; otherwise, false. The default is true. - - - - - Initializes a new instance of the class. - - - - - Instructs the not to serialize the public field or public read/write property value. - - - - - Base class for a table of atomized string objects. - - - - - Gets a string containing the same characters as the specified range of characters in the given array. - - The character array containing the name to find. - The zero-based index into the array specifying the first character of the name. - The number of characters in the name. - A string containing the same characters as the specified range of characters in the given array. - - - - Instructs the how to serialize the object. - - - - - Gets or sets the member serialization. - - The member serialization. - - - - Gets or sets the missing member handling used when deserializing this object. - - The missing member handling. - - - - Gets or sets how the object's properties with null values are handled during serialization and deserialization. - - How the object's properties with null values are handled during serialization and deserialization. - - - - Gets or sets a value that indicates whether the object's properties are required. - - - A value indicating whether the object's properties are required. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified member serialization. - - The member serialization. - - - - Initializes a new instance of the class with the specified container Id. - - The container Id. - - - - Instructs the to always serialize the member with the specified name. - - - - - Gets or sets the type used when serializing the property's collection items. - - The collection's items type. - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonProperty(ItemConverterType = typeof(MyContainerConverter), ItemConverterParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the of the . - - The of the . - - - - The parameter list to use when constructing the described by . - If null, the default constructor is used. - When non-null, there must be a constructor defined in the that exactly matches the number, - order, and type of these parameters. - - - - [JsonProperty(NamingStrategyType = typeof(MyNamingStrategy), NamingStrategyParameters = new object[] { 123, "Four" })] - - - - - - Gets or sets the null value handling used when serializing this property. - - The null value handling. - - - - Gets or sets the default value handling used when serializing this property. - - The default value handling. - - - - Gets or sets the reference loop handling used when serializing this property. - - The reference loop handling. - - - - Gets or sets the object creation handling used when deserializing this property. - - The object creation handling. - - - - Gets or sets the type name handling used when serializing this property. - - The type name handling. - - - - Gets or sets whether this property's value is serialized as a reference. - - Whether this property's value is serialized as a reference. - - - - Gets or sets the order of serialization of a member. - - The numeric order of serialization. - - - - Gets or sets a value indicating whether this property is required. - - - A value indicating whether this property is required. - - - - - Gets or sets the name of the property. - - The name of the property. - - - - Gets or sets the reference loop handling used when serializing the property's collection items. - - The collection's items reference loop handling. - - - - Gets or sets the type name handling used when serializing the property's collection items. - - The collection's items type name handling. - - - - Gets or sets whether this property's collection items are serialized as a reference. - - Whether this property's collection items are serialized as a reference. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class with the specified name. - - Name of the property. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. - - - - - Asynchronously reads the next JSON token from the source. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns true if the next token was read successfully; false if there are no more tokens to read. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously skips the children of the current token. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a []. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the []. This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously reads the next JSON token from the source as a . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the . This result will be null at the end of an array. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Specifies the state of the reader. - - - - - A read method has not been called. - - - - - The end of the file has been reached successfully. - - - - - Reader is at a property. - - - - - Reader is at the start of an object. - - - - - Reader is in an object. - - - - - Reader is at the start of an array. - - - - - Reader is in an array. - - - - - The method has been called. - - - - - Reader has just read a value. - - - - - Reader is at the start of a constructor. - - - - - Reader is in a constructor. - - - - - An error occurred that prevents the read operation from continuing. - - - - - The end of the file has been reached successfully. - - - - - Gets the current reader state. - - The current reader state. - - - - Gets or sets a value indicating whether the source should be closed when this reader is closed. - - - true to close the source when this reader is closed; otherwise false. The default is true. - - - - - Gets or sets a value indicating whether multiple pieces of JSON content can - be read from a continuous stream without erroring. - - - true to support reading multiple pieces of JSON content; otherwise false. - The default is false. - - - - - Gets the quotation mark character used to enclose the value of a string. - - - - - Gets or sets how time zones are handled when reading JSON. - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - - - - - Gets or sets how custom date formatted strings are parsed when reading JSON. - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - - - - - Gets the type of the current JSON token. - - - - - Gets the text value of the current JSON token. - - - - - Gets the .NET type for the current JSON token. - - - - - Gets the depth of the current token in the JSON document. - - The depth of the current token in the JSON document. - - - - Gets the path of the current JSON token. - - - - - Gets or sets the culture used when reading JSON. Defaults to . - - - - - Initializes a new instance of the class. - - - - - Reads the next JSON token from the source. - - true if the next token was read successfully; false if there are no more tokens to read. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a []. - - A [] or null if the next JSON token is null. This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the source as a of . - - A of . This method will return null at the end of an array. - - - - Skips the children of the current token. - - - - - Sets the current token. - - The new token. - - - - Sets the current token and value. - - The new token. - The value. - - - - Sets the current token and value. - - The new token. - The value. - A flag indicating whether the position index inside an array should be updated. - - - - Sets the state based on current token type. - - - - - Releases unmanaged and - optionally - managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Changes the reader's state to . - If is set to true, the source is also closed. - - - - - The exception thrown when an error occurs while reading JSON text. - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class. - - The that holds the serialized object data about the exception being thrown. - The that contains contextual information about the source or destination. - The parameter is null. - The class name is null or is zero (0). - - - - Initializes a new instance of the class - with a specified error message, JSON path, line number, line position, and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The line number indicating where the error occurred. - The line position indicating where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Instructs the to always serialize the member, and to require that the member has a value. - - - - - The exception thrown when an error occurs during JSON serialization or deserialization. - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class. - - The that holds the serialized object data about the exception being thrown. - The that contains contextual information about the source or destination. - The parameter is null. - The class name is null or is zero (0). - - - - Initializes a new instance of the class - with a specified error message, JSON path, line number, line position, and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The line number indicating where the error occurred. - The line position indicating where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Serializes and deserializes objects into and from the JSON format. - The enables you to control how objects are encoded into JSON. - - - - - Occurs when the errors during serialization and deserialization. - - - - - Gets or sets the used by the serializer when resolving references. - - - - - Gets or sets the used by the serializer when resolving type names. - - - - - Gets or sets the used by the serializer when resolving type names. - - - - - Gets or sets the used by the serializer when writing trace messages. - - The trace writer. - - - - Gets or sets the equality comparer used by the serializer when comparing references. - - The equality comparer. - - - - Gets or sets how type name writing and reading is handled by the serializer. - The default value is . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how object references are preserved by the serializer. - The default value is . - - - - - Gets or sets how reference loops (e.g. a class referencing itself) is handled. - The default value is . - - - - - Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. - The default value is . - - - - - Gets or sets how null values are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how default values are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how objects are created during deserialization. - The default value is . - - The object creation handling. - - - - Gets or sets how constructors are used during deserialization. - The default value is . - - The constructor handling. - - - - Gets or sets how metadata properties are used during deserialization. - The default value is . - - The metadata properties handling. - - - - Gets a collection that will be used during serialization. - - Collection that will be used during serialization. - - - - Gets or sets the contract resolver used by the serializer when - serializing .NET objects to JSON and vice versa. - - - - - Gets or sets the used by the serializer when invoking serialization callback methods. - - The context. - - - - Indicates how JSON text output is formatted. - The default value is . - - - - - Gets or sets how dates are written to JSON text. - The default value is . - - - - - Gets or sets how time zones are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - The default value is . - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - The default value is . - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written as JSON text. - The default value is . - - - - - Gets or sets how strings are escaped when writing JSON text. - The default value is . - - - - - Gets or sets how and values are formatted when writing JSON text, - and the expected date format when reading JSON text. - The default value is "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK". - - - - - Gets or sets the culture used when reading JSON. - The default value is . - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - A null value means there is no maximum. - The default value is null. - - - - - Gets a value indicating whether there will be a check for additional JSON content after deserializing an object. - The default value is false. - - - true if there will be a check for additional JSON content after deserializing an object; otherwise, false. - - - - - Initializes a new instance of the class. - - - - - Creates a new instance. - The will not use default settings - from . - - - A new instance. - The will not use default settings - from . - - - - - Creates a new instance using the specified . - The will not use default settings - from . - - The settings to be applied to the . - - A new instance using the specified . - The will not use default settings - from . - - - - - Creates a new instance. - The will use default settings - from . - - - A new instance. - The will use default settings - from . - - - - - Creates a new instance using the specified . - The will use default settings - from as well as the specified . - - The settings to be applied to the . - - A new instance using the specified . - The will use default settings - from as well as the specified . - - - - - Populates the JSON values onto the target object. - - The that contains the JSON structure to read values from. - The target object to populate values onto. - - - - Populates the JSON values onto the target object. - - The that contains the JSON structure to read values from. - The target object to populate values onto. - - - - Deserializes the JSON structure contained by the specified . - - The that contains the JSON structure to deserialize. - The being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The of object being deserialized. - The instance of being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The type of the object to deserialize. - The instance of being deserialized. - - - - Deserializes the JSON structure contained by the specified - into an instance of the specified type. - - The containing the object. - The of object being deserialized. - The instance of being deserialized. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - The type of the value being serialized. - This parameter is used when is Auto to write out the type name if the type of the value does not match. - Specifying the type is optional. - - - - - Serializes the specified and writes the JSON structure - using the specified . - - The used to write the JSON structure. - The to serialize. - - - - Specifies the settings on a object. - - - - - Gets or sets how reference loops (e.g. a class referencing itself) are handled. - The default value is . - - Reference loop handling. - - - - Gets or sets how missing members (e.g. JSON contains a property that isn't a member on the object) are handled during deserialization. - The default value is . - - Missing member handling. - - - - Gets or sets how objects are created during deserialization. - The default value is . - - The object creation handling. - - - - Gets or sets how null values are handled during serialization and deserialization. - The default value is . - - Null value handling. - - - - Gets or sets how default values are handled during serialization and deserialization. - The default value is . - - The default value handling. - - - - Gets or sets a collection that will be used during serialization. - - The converters. - - - - Gets or sets how object references are preserved by the serializer. - The default value is . - - The preserve references handling. - - - - Gets or sets how type name writing and reading is handled by the serializer. - The default value is . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - The type name handling. - - - - Gets or sets how metadata properties are used during deserialization. - The default value is . - - The metadata properties handling. - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how a type name assembly is written and resolved by the serializer. - The default value is . - - The type name assembly format. - - - - Gets or sets how constructors are used during deserialization. - The default value is . - - The constructor handling. - - - - Gets or sets the contract resolver used by the serializer when - serializing .NET objects to JSON and vice versa. - - The contract resolver. - - - - Gets or sets the equality comparer used by the serializer when comparing references. - - The equality comparer. - - - - Gets or sets the used by the serializer when resolving references. - - The reference resolver. - - - - Gets or sets a function that creates the used by the serializer when resolving references. - - A function that creates the used by the serializer when resolving references. - - - - Gets or sets the used by the serializer when writing trace messages. - - The trace writer. - - - - Gets or sets the used by the serializer when resolving type names. - - The binder. - - - - Gets or sets the used by the serializer when resolving type names. - - The binder. - - - - Gets or sets the error handler called during serialization and deserialization. - - The error handler called during serialization and deserialization. - - - - Gets or sets the used by the serializer when invoking serialization callback methods. - - The context. - - - - Gets or sets how and values are formatted when writing JSON text, - and the expected date format when reading JSON text. - The default value is "yyyy'-'MM'-'dd'T'HH':'mm':'ss.FFFFFFFK". - - - - - Gets or sets the maximum depth allowed when reading JSON. Reading past this depth will throw a . - A null value means there is no maximum. - The default value is null. - - - - - Indicates how JSON text output is formatted. - The default value is . - - - - - Gets or sets how dates are written to JSON text. - The default value is . - - - - - Gets or sets how time zones are handled during serialization and deserialization. - The default value is . - - - - - Gets or sets how date formatted strings, e.g. "\/Date(1198908717056)\/" and "2012-03-21T05:40Z", are parsed when reading JSON. - The default value is . - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written as JSON. - The default value is . - - - - - Gets or sets how floating point numbers, e.g. 1.0 and 9.9, are parsed when reading JSON text. - The default value is . - - - - - Gets or sets how strings are escaped when writing JSON text. - The default value is . - - - - - Gets or sets the culture used when reading JSON. - The default value is . - - - - - Gets a value indicating whether there will be a check for additional content after deserializing an object. - The default value is false. - - - true if there will be a check for additional content after deserializing an object; otherwise, false. - - - - - Initializes a new instance of the class. - - - - - Represents a reader that provides fast, non-cached, forward-only access to JSON text data. - - - - - Asynchronously reads the next JSON token from the source. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns true if the next token was read successfully; false if there are no more tokens to read. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a []. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the []. This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a of . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the of . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously reads the next JSON token from the source as a . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous read. The - property returns the . This result will be null at the end of an array. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Initializes a new instance of the class with the specified . - - The containing the JSON data to read. - - - - Gets or sets the reader's property name table. - - - - - Gets or sets the reader's character buffer pool. - - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a []. - - A [] or null if the next JSON token is null. This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Gets a value indicating whether the class can return line information. - - - true if and can be provided; otherwise, false. - - - - - Gets the current line number. - - - The current line number or 0 if no line information is available (for example, returns false). - - - - - Gets the current line position. - - - The current line position or 0 if no line information is available (for example, returns false). - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Asynchronously flushes whatever is in the buffer to the destination and also flushes the destination. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the JSON value delimiter. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the specified end token. - - The end token to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously closes this writer. - If is set to true, the destination is also closed. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of the current JSON object or array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes indent characters. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes an indent space. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes raw JSON without changing the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a null value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the beginning of a JSON array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the beginning of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the start of a constructor with the given name. - - The name of the constructor. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes an undefined value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the given white space. - - The string of white space characters. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a [] value. - - The [] value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of an array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of a constructor. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes the end of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Asynchronously writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - Derived classes must override this method to get asynchronous behaviour. Otherwise it will - execute synchronously, returning an already-completed task. - - - - Gets or sets the writer's character array pool. - - - - - Gets or sets how many s to write for each level in the hierarchy when is set to . - - - - - Gets or sets which character to use to quote attribute values. - - - - - Gets or sets which character to use for indenting when is set to . - - - - - Gets or sets a value indicating whether object names will be surrounded with quotes. - - - - - Initializes a new instance of the class using the specified . - - The to write to. - - - - Flushes whatever is in the buffer to the underlying and also flushes the underlying . - - - - - Closes this writer. - If is set to true, the underlying is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the specified end token. - - The end token to write. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - - - - Writes indent characters. - - - - - Writes the JSON value delimiter. - - - - - Writes an indent space. - - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the given white space. - - The string of white space characters. - - - - Specifies the type of JSON token. - - - - - This is returned by the if a read method has not been called. - - - - - An object start token. - - - - - An array start token. - - - - - A constructor start token. - - - - - An object property name. - - - - - A comment. - - - - - Raw JSON. - - - - - An integer. - - - - - A float. - - - - - A string. - - - - - A boolean. - - - - - A null token. - - - - - An undefined token. - - - - - An object end token. - - - - - An array end token. - - - - - A constructor end token. - - - - - A Date. - - - - - Byte data. - - - - - - Represents a reader that provides validation. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Sets an event handler for receiving schema validation errors. - - - - - Gets the text value of the current JSON token. - - - - - - Gets the depth of the current token in the JSON document. - - The depth of the current token in the JSON document. - - - - Gets the path of the current JSON token. - - - - - Gets the quotation mark character used to enclose the value of a string. - - - - - - Gets the type of the current JSON token. - - - - - - Gets the .NET type for the current JSON token. - - - - - - Initializes a new instance of the class that - validates the content returned from the given . - - The to read from while validating. - - - - Gets or sets the schema. - - The schema. - - - - Gets the used to construct this . - - The specified in the constructor. - - - - Changes the reader's state to . - If is set to true, the underlying is also closed. - - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a []. - - - A [] or null if the next JSON token is null. - - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying as a . - - A . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . This method will return null at the end of an array. - - - - Reads the next JSON token from the underlying as a of . - - A of . - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Asynchronously closes this writer. - If is set to true, the destination is also closed. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously flushes whatever is in the buffer to the destination and also flushes the destination. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the specified end token. - - The end token to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes indent characters. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the JSON value delimiter. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes an indent space. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes raw JSON without changing the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of the current JSON object or array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of an array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of a constructor. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the end of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a null value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the beginning of a JSON array. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the start of a constructor with the given name. - - The name of the constructor. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the beginning of a JSON object. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the current token. - - The to read the token from. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the current token. - - The to read the token from. - A flag indicating whether the current token's children should be written. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the token and its value. - - The to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the token and its value. - - The to write. - - The value to write. - A value is only required for tokens that have an associated value, e.g. the property name for . - null can be passed to the method for tokens that don't have a value, e.g. . - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a [] value. - - The [] value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a value. - - The value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes a of value. - - The of value to write. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes an undefined value. - - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously writes the given white space. - - The string of white space characters. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Asynchronously ets the state of the . - - The being written. - The value being written. - The token to monitor for cancellation requests. The default value is . - A that represents the asynchronous operation. - The default behaviour is to execute synchronously, returning an already-completed task. Derived - classes can override this behaviour for true asynchronicity. - - - - Gets or sets a value indicating whether the destination should be closed when this writer is closed. - - - true to close the destination when this writer is closed; otherwise false. The default is true. - - - - - Gets or sets a value indicating whether the JSON should be auto-completed when this writer is closed. - - - true to auto-complete the JSON when this writer is closed; otherwise false. The default is true. - - - - - Gets the top. - - The top. - - - - Gets the state of the writer. - - - - - Gets the path of the writer. - - - - - Gets or sets a value indicating how JSON text output should be formatted. - - - - - Gets or sets how dates are written to JSON text. - - - - - Gets or sets how time zones are handled when writing JSON text. - - - - - Gets or sets how strings are escaped when writing JSON text. - - - - - Gets or sets how special floating point numbers, e.g. , - and , - are written to JSON text. - - - - - Gets or sets how and values are formatted when writing JSON text. - - - - - Gets or sets the culture used when writing JSON. Defaults to . - - - - - Initializes a new instance of the class. - - - - - Flushes whatever is in the buffer to the destination and also flushes the destination. - - - - - Closes this writer. - If is set to true, the destination is also closed. - If is set to true, the JSON is auto-completed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the end of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the end of an array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the end constructor. - - - - - Writes the property name of a name/value pair of a JSON object. - - The name of the property. - - - - Writes the property name of a name/value pair of a JSON object. - - The name of the property. - A flag to indicate whether the text should be escaped when it is written as a JSON property name. - - - - Writes the end of the current JSON object or array. - - - - - Writes the current token and its children. - - The to read the token from. - - - - Writes the current token. - - The to read the token from. - A flag indicating whether the current token's children should be written. - - - - Writes the token and its value. - - The to write. - - The value to write. - A value is only required for tokens that have an associated value, e.g. the property name for . - null can be passed to the method for tokens that don't have a value, e.g. . - - - - - Writes the token. - - The to write. - - - - Writes the specified end token. - - The end token to write. - - - - Writes indent characters. - - - - - Writes the JSON value delimiter. - - - - - Writes an indent space. - - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON without changing the writer's state. - - The raw JSON to write. - - - - Writes raw JSON where a value is expected and updates the writer's state. - - The raw JSON to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a of value. - - The of value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - An error will raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes the given white space. - - The string of white space characters. - - - - Releases unmanaged and - optionally - managed resources. - - true to release both managed and unmanaged resources; false to release only unmanaged resources. - - - - Sets the state of the . - - The being written. - The value being written. - - - - The exception thrown when an error occurs while writing JSON text. - - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class. - - The that holds the serialized object data about the exception being thrown. - The that contains contextual information about the source or destination. - The parameter is null. - The class name is null or is zero (0). - - - - Initializes a new instance of the class - with a specified error message, JSON path and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The path to the JSON where the error occurred. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Specifies how JSON comments are handled when loading JSON. - - - - - Ignore comments. - - - - - Load comments as a with type . - - - - - Specifies how duplicate property names are handled when loading JSON. - - - - - Replace the existing value when there is a duplicate property. The value of the last property in the JSON object will be used. - - - - - Ignore the new value when there is a duplicate property. The value of the first property in the JSON object will be used. - - - - - Throw a when a duplicate property is encountered. - - - - - Contains the LINQ to JSON extension methods. - - - - - Returns a collection of tokens that contains the ancestors of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains the ancestors of every token in the source collection. - - - - Returns a collection of tokens that contains every token in the source collection, and the ancestors of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains every token in the source collection, the ancestors of every token in the source collection. - - - - Returns a collection of tokens that contains the descendants of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains the descendants of every token in the source collection. - - - - Returns a collection of tokens that contains every token in the source collection, and the descendants of every token in the source collection. - - The type of the objects in source, constrained to . - An of that contains the source collection. - An of that contains every token in the source collection, and the descendants of every token in the source collection. - - - - Returns a collection of child properties of every object in the source collection. - - An of that contains the source collection. - An of that contains the properties of every object in the source collection. - - - - Returns a collection of child values of every object in the source collection with the given key. - - An of that contains the source collection. - The token key. - An of that contains the values of every token in the source collection with the given key. - - - - Returns a collection of child values of every object in the source collection. - - An of that contains the source collection. - An of that contains the values of every token in the source collection. - - - - Returns a collection of converted child values of every object in the source collection with the given key. - - The type to convert the values to. - An of that contains the source collection. - The token key. - An that contains the converted values of every token in the source collection with the given key. - - - - Returns a collection of converted child values of every object in the source collection. - - The type to convert the values to. - An of that contains the source collection. - An that contains the converted values of every token in the source collection. - - - - Converts the value. - - The type to convert the value to. - A cast as a of . - A converted value. - - - - Converts the value. - - The source collection type. - The type to convert the value to. - A cast as a of . - A converted value. - - - - Returns a collection of child tokens of every array in the source collection. - - The source collection type. - An of that contains the source collection. - An of that contains the values of every token in the source collection. - - - - Returns a collection of converted child tokens of every array in the source collection. - - An of that contains the source collection. - The type to convert the values to. - The source collection type. - An that contains the converted values of every token in the source collection. - - - - Returns the input typed as . - - An of that contains the source collection. - The input typed as . - - - - Returns the input typed as . - - The source collection type. - An of that contains the source collection. - The input typed as . - - - - Represents a collection of objects. - - The type of token. - - - - Gets the of with the specified key. - - - - - - Represents a JSON array. - - - - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous load. The property contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous load. The property contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified content. - - The contents of the array. - - - - Initializes a new instance of the class with the specified content. - - The contents of the array. - - - - Loads an from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads an from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - - - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - - - - - - Creates a from an object. - - The object that will be used to create . - A with the values of the specified object. - - - - Creates a from an object. - - The object that will be used to create . - The that will be used to read the object. - A with the values of the specified object. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets or sets the at the specified index. - - - - - - Determines the index of a specific item in the . - - The object to locate in the . - - The index of if found in the list; otherwise, -1. - - - - - Inserts an item to the at the specified index. - - The zero-based index at which should be inserted. - The object to insert into the . - - is not a valid index in the . - - - - - Removes the item at the specified index. - - The zero-based index of the item to remove. - - is not a valid index in the . - - - - - Returns an enumerator that iterates through the collection. - - - A of that can be used to iterate through the collection. - - - - - Adds an item to the . - - The object to add to the . - - - - Removes all items from the . - - - - - Determines whether the contains a specific value. - - The object to locate in the . - - true if is found in the ; otherwise, false. - - - - - Copies the elements of the to an array, starting at a particular array index. - - The array. - Index of the array. - - - - Gets a value indicating whether the is read-only. - - true if the is read-only; otherwise, false. - - - - Removes the first occurrence of a specific object from the . - - The object to remove from the . - - true if was successfully removed from the ; otherwise, false. This method also returns false if is not found in the original . - - - - - Represents a JSON constructor. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets or sets the name of this constructor. - - The constructor name. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified name and content. - - The constructor name. - The contents of the constructor. - - - - Initializes a new instance of the class with the specified name and content. - - The constructor name. - The contents of the constructor. - - - - Initializes a new instance of the class with the specified name. - - The constructor name. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified key. - - The with the specified key. - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Represents a token that can contain other tokens. - - - - - Occurs when the list changes or an item in the list changes. - - - - - Occurs before an item is added to the collection. - - - - - Occurs when the items list of the collection has changed, or the collection is reset. - - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Raises the event. - - The instance containing the event data. - - - - Raises the event. - - The instance containing the event data. - - - - Raises the event. - - The instance containing the event data. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Get the first child token of this token. - - - A containing the first child token of the . - - - - - Get the last child token of this token. - - - A containing the last child token of the . - - - - - Returns a collection of the child tokens of this token, in document order. - - - An of containing the child tokens of this , in document order. - - - - - Returns a collection of the child values of this token, in document order. - - The type to convert the values to. - - A containing the child values of this , in document order. - - - - - Returns a collection of the descendant tokens for this token in document order. - - An of containing the descendant tokens of the . - - - - Returns a collection of the tokens that contain this token, and all descendant tokens of this token, in document order. - - An of containing this token, and all the descendant tokens of the . - - - - Adds the specified content as children of this . - - The content to be added. - - - - Adds the specified content as the first children of this . - - The content to be added. - - - - Creates a that can be used to add tokens to the . - - A that is ready to have content written to it. - - - - Replaces the child nodes of this token with the specified content. - - The content. - - - - Removes the child nodes from this token. - - - - - Merge the specified content into this . - - The content to be merged. - - - - Merge the specified content into this using . - - The content to be merged. - The used to merge the content. - - - - Gets the count of child JSON tokens. - - The count of child JSON tokens. - - - - Represents a collection of objects. - - The type of token. - - - - An empty collection of objects. - - - - - Initializes a new instance of the struct. - - The enumerable. - - - - Returns an enumerator that can be used to iterate through the collection. - - - A that can be used to iterate through the collection. - - - - - Gets the of with the specified key. - - - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Determines whether the specified is equal to this instance. - - The to compare with this instance. - - true if the specified is equal to this instance; otherwise, false. - - - - - Returns a hash code for this instance. - - - A hash code for this instance, suitable for use in hashing algorithms and data structures like a hash table. - - - - - Represents a JSON object. - - - - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous load. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Occurs when a property value changes. - - - - - Occurs when a property value is changing. - - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the specified content. - - The contents of the object. - - - - Initializes a new instance of the class with the specified content. - - The contents of the object. - - - - Gets the node type for this . - - The type. - - - - Gets an of of this object's properties. - - An of of this object's properties. - - - - Gets a with the specified name. - - The property name. - A with the specified name or null. - - - - Gets the with the specified name. - The exact name will be searched for first and if no matching property is found then - the will be used to match a property. - - The property name. - One of the enumeration values that specifies how the strings will be compared. - A matched with the specified name or null. - - - - Gets a of of this object's property values. - - A of of this object's property values. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets or sets the with the specified property name. - - - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - is not valid JSON. - - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - is not valid JSON. - - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - is not valid JSON. - - - - - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - is not valid JSON. - - - - - - - - Creates a from an object. - - The object that will be used to create . - A with the values of the specified object. - - - - Creates a from an object. - - The object that will be used to create . - The that will be used to read the object. - A with the values of the specified object. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Gets the with the specified property name. - - Name of the property. - The with the specified property name. - - - - Gets the with the specified property name. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - Name of the property. - One of the enumeration values that specifies how the strings will be compared. - The with the specified property name. - - - - Tries to get the with the specified property name. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - Name of the property. - The value. - One of the enumeration values that specifies how the strings will be compared. - true if a value was successfully retrieved; otherwise, false. - - - - Adds the specified property name. - - Name of the property. - The value. - - - - Determines whether the JSON object has the specified property name. - - Name of the property. - true if the JSON object has the specified property name; otherwise, false. - - - - Removes the property with the specified name. - - Name of the property. - true if item was successfully removed; otherwise, false. - - - - Tries to get the with the specified property name. - - Name of the property. - The value. - true if a value was successfully retrieved; otherwise, false. - - - - Returns an enumerator that can be used to iterate through the collection. - - - A that can be used to iterate through the collection. - - - - - Raises the event with the provided arguments. - - Name of the property. - - - - Raises the event with the provided arguments. - - Name of the property. - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Represents a JSON property. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns a that contains the JSON that was read from the specified . - - - - Asynchronously loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns a that contains the JSON that was read from the specified . - - - - Gets the container's children tokens. - - The container's children tokens. - - - - Gets the property name. - - The property name. - - - - Gets or sets the property value. - - The property value. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Gets the node type for this . - - The type. - - - - Initializes a new instance of the class. - - The property name. - The property content. - - - - Initializes a new instance of the class. - - The property name. - The property content. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Loads a from a . - - A that will be read for the content of the . - A that contains the JSON that was read from the specified . - - - - Loads a from a . - - A that will be read for the content of the . - The used to load the JSON. - If this is null, default load settings will be used. - A that contains the JSON that was read from the specified . - - - - Represents a view of a . - - - - - Initializes a new instance of the class. - - The name. - - - - When overridden in a derived class, returns whether resetting an object changes its value. - - - true if resetting the component changes its value; otherwise, false. - - The component to test for reset capability. - - - - When overridden in a derived class, gets the current value of the property on a component. - - - The value of a property for a given component. - - The component with the property for which to retrieve the value. - - - - When overridden in a derived class, resets the value for this property of the component to the default value. - - The component with the property value that is to be reset to the default value. - - - - When overridden in a derived class, sets the value of the component to a different value. - - The component with the property value that is to be set. - The new value. - - - - When overridden in a derived class, determines a value indicating whether the value of this property needs to be persisted. - - - true if the property should be persisted; otherwise, false. - - The component with the property to be examined for persistence. - - - - When overridden in a derived class, gets the type of the component this property is bound to. - - - A that represents the type of component this property is bound to. - When the or - - methods are invoked, the object specified might be an instance of this type. - - - - - When overridden in a derived class, gets a value indicating whether this property is read-only. - - - true if the property is read-only; otherwise, false. - - - - - When overridden in a derived class, gets the type of the property. - - - A that represents the type of the property. - - - - - Gets the hash code for the name of the member. - - - - The hash code for the name of the member. - - - - - Represents a raw JSON string. - - - - - Asynchronously creates an instance of with the content of the reader's current token. - - The reader. - The token to monitor for cancellation requests. The default value is . - A representing the asynchronous creation. The - property returns an instance of with the content of the reader's current token. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class. - - The raw json. - - - - Creates an instance of with the content of the reader's current token. - - The reader. - An instance of with the content of the reader's current token. - - - - Specifies the settings used when loading JSON. - - - - - Initializes a new instance of the class. - - - - - Gets or sets how JSON comments are handled when loading JSON. - The default value is . - - The JSON comment handling. - - - - Gets or sets how JSON line info is handled when loading JSON. - The default value is . - - The JSON line info handling. - - - - Gets or sets how duplicate property names in JSON objects are handled when loading JSON. - The default value is . - - The JSON duplicate property name handling. - - - - Specifies the settings used when merging JSON. - - - - - Initializes a new instance of the class. - - - - - Gets or sets the method used when merging JSON arrays. - - The method used when merging JSON arrays. - - - - Gets or sets how null value properties are merged. - - How null value properties are merged. - - - - Gets or sets the comparison used to match property names while merging. - The exact property name will be searched for first and if no matching property is found then - the will be used to match a property. - - The comparison used to match property names while merging. - - - - Represents an abstract JSON token. - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Writes this token to a asynchronously. - - A into which this method will write. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Asynchronously creates a from a . - - An positioned at the token to read into this . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains - the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - An positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains - the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - A positioned at the token to read into this . - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Asynchronously creates a from a . - - A positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - The token to monitor for cancellation requests. The default value is . - - A that represents the asynchronous creation. The - property returns a that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Gets a comparer that can compare two tokens for value equality. - - A that can compare two nodes for value equality. - - - - Gets or sets the parent. - - The parent. - - - - Gets the root of this . - - The root of this . - - - - Gets the node type for this . - - The type. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Compares the values of two tokens, including the values of all descendant tokens. - - The first to compare. - The second to compare. - true if the tokens are equal; otherwise false. - - - - Gets the next sibling token of this node. - - The that contains the next sibling token. - - - - Gets the previous sibling token of this node. - - The that contains the previous sibling token. - - - - Gets the path of the JSON token. - - - - - Adds the specified content immediately after this token. - - A content object that contains simple content or a collection of content objects to be added after this token. - - - - Adds the specified content immediately before this token. - - A content object that contains simple content or a collection of content objects to be added before this token. - - - - Returns a collection of the ancestor tokens of this token. - - A collection of the ancestor tokens of this token. - - - - Returns a collection of tokens that contain this token, and the ancestors of this token. - - A collection of tokens that contain this token, and the ancestors of this token. - - - - Returns a collection of the sibling tokens after this token, in document order. - - A collection of the sibling tokens after this tokens, in document order. - - - - Returns a collection of the sibling tokens before this token, in document order. - - A collection of the sibling tokens before this token, in document order. - - - - Gets the with the specified key. - - The with the specified key. - - - - Gets the with the specified key converted to the specified type. - - The type to convert the token to. - The token key. - The converted token value. - - - - Get the first child token of this token. - - A containing the first child token of the . - - - - Get the last child token of this token. - - A containing the last child token of the . - - - - Returns a collection of the child tokens of this token, in document order. - - An of containing the child tokens of this , in document order. - - - - Returns a collection of the child tokens of this token, in document order, filtered by the specified type. - - The type to filter the child tokens on. - A containing the child tokens of this , in document order. - - - - Returns a collection of the child values of this token, in document order. - - The type to convert the values to. - A containing the child values of this , in document order. - - - - Removes this token from its parent. - - - - - Replaces this token with the specified token. - - The value. - - - - Writes this token to a . - - A into which this method will write. - A collection of which will be used when writing the token. - - - - Returns the indented JSON for this token. - - - The indented JSON for this token. - - - - - Returns the JSON for this token using the given formatting and converters. - - Indicates how the output should be formatted. - A collection of s which will be used when writing the token. - The JSON for this token using the given formatting and converters. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to []. - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to of . - - The value. - The result of the conversion. - - - - Performs an explicit conversion from to . - - The value. - The result of the conversion. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from [] to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from to . - - The value to create a from. - The initialized with the specified value. - - - - Performs an implicit conversion from of to . - - The value to create a from. - The initialized with the specified value. - - - - Creates a for this token. - - A that can be used to read this token and its descendants. - - - - Creates a from an object. - - The object that will be used to create . - A with the value of the specified object. - - - - Creates a from an object using the specified . - - The object that will be used to create . - The that will be used when reading the object. - A with the value of the specified object. - - - - Creates an instance of the specified .NET type from the . - - The object type that the token will be deserialized to. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the . - - The object type that the token will be deserialized to. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the using the specified . - - The object type that the token will be deserialized to. - The that will be used when creating the object. - The new object created from the JSON value. - - - - Creates an instance of the specified .NET type from the using the specified . - - The object type that the token will be deserialized to. - The that will be used when creating the object. - The new object created from the JSON value. - - - - Creates a from a . - - A positioned at the token to read into this . - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Creates a from a . - - An positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Load a from a string that contains JSON. - - A that contains JSON. - A populated from the string that contains JSON. - - - - Load a from a string that contains JSON. - - A that contains JSON. - The used to load the JSON. - If this is null, default load settings will be used. - A populated from the string that contains JSON. - - - - Creates a from a . - - A positioned at the token to read into this . - The used to load the JSON. - If this is null, default load settings will be used. - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Creates a from a . - - A positioned at the token to read into this . - - A that contains the token and its descendant tokens - that were read from the reader. The runtime type of the token is determined - by the token type of the first token encountered in the reader. - - - - - Selects a using a JPath expression. Selects the token that matches the object path. - - - A that contains a JPath expression. - - A , or null. - - - - Selects a using a JPath expression. Selects the token that matches the object path. - - - A that contains a JPath expression. - - A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. - A . - - - - Selects a collection of elements using a JPath expression. - - - A that contains a JPath expression. - - An of that contains the selected elements. - - - - Selects a collection of elements using a JPath expression. - - - A that contains a JPath expression. - - A flag to indicate whether an error should be thrown if no tokens are found when evaluating part of the expression. - An of that contains the selected elements. - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Creates a new instance of the . All child tokens are recursively cloned. - - A new instance of the . - - - - Adds an object to the annotation list of this . - - The annotation to add. - - - - Get the first annotation object of the specified type from this . - - The type of the annotation to retrieve. - The first annotation object that matches the specified type, or null if no annotation is of the specified type. - - - - Gets the first annotation object of the specified type from this . - - The of the annotation to retrieve. - The first annotation object that matches the specified type, or null if no annotation is of the specified type. - - - - Gets a collection of annotations of the specified type for this . - - The type of the annotations to retrieve. - An that contains the annotations for this . - - - - Gets a collection of annotations of the specified type for this . - - The of the annotations to retrieve. - An of that contains the annotations that match the specified type for this . - - - - Removes the annotations of the specified type from this . - - The type of annotations to remove. - - - - Removes the annotations of the specified type from this . - - The of annotations to remove. - - - - Compares tokens to determine whether they are equal. - - - - - Determines whether the specified objects are equal. - - The first object of type to compare. - The second object of type to compare. - - true if the specified objects are equal; otherwise, false. - - - - - Returns a hash code for the specified object. - - The for which a hash code is to be returned. - A hash code for the specified object. - The type of is a reference type and is null. - - - - Represents a reader that provides fast, non-cached, forward-only access to serialized JSON data. - - - - - Gets the at the reader's current position. - - - - - Initializes a new instance of the class. - - The token to read from. - - - - Initializes a new instance of the class. - - The token to read from. - The initial path of the token. It is prepended to the returned . - - - - Reads the next JSON token from the underlying . - - - true if the next token was read successfully; false if there are no more tokens to read. - - - - - Gets the path of the current JSON token. - - - - - Specifies the type of token. - - - - - No token type has been set. - - - - - A JSON object. - - - - - A JSON array. - - - - - A JSON constructor. - - - - - A JSON object property. - - - - - A comment. - - - - - An integer value. - - - - - A float value. - - - - - A string value. - - - - - A boolean value. - - - - - A null value. - - - - - An undefined value. - - - - - A date value. - - - - - A raw JSON value. - - - - - A collection of bytes value. - - - - - A Guid value. - - - - - A Uri value. - - - - - A TimeSpan value. - - - - - Represents a writer that provides a fast, non-cached, forward-only way of generating JSON data. - - - - - Gets the at the writer's current position. - - - - - Gets the token being written. - - The token being written. - - - - Initializes a new instance of the class writing to the given . - - The container being written to. - - - - Initializes a new instance of the class. - - - - - Flushes whatever is in the buffer to the underlying . - - - - - Closes this writer. - If is set to true, the JSON is auto-completed. - - - Setting to true has no additional effect, since the underlying is a type that cannot be closed. - - - - - Writes the beginning of a JSON object. - - - - - Writes the beginning of a JSON array. - - - - - Writes the start of a constructor with the given name. - - The name of the constructor. - - - - Writes the end. - - The token. - - - - Writes the property name of a name/value pair on a JSON object. - - The name of the property. - - - - Writes a value. - An error will be raised if the value cannot be written as a single JSON token. - - The value to write. - - - - Writes a null value. - - - - - Writes an undefined value. - - - - - Writes raw JSON. - - The raw JSON to write. - - - - Writes a comment /*...*/ containing the specified text. - - Text to place inside the comment. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a [] value. - - The [] value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Writes a value. - - The value to write. - - - - Represents a value in JSON (string, integer, date, etc). - - - - - Writes this token to a asynchronously. - - A into which this method will write. - The token to monitor for cancellation requests. - A collection of which will be used when writing the token. - A that represents the asynchronous write operation. - - - - Initializes a new instance of the class from another object. - - A object to copy from. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Initializes a new instance of the class with the given value. - - The value. - - - - Gets a value indicating whether this token has child tokens. - - - true if this token has child values; otherwise, false. - - - - - Creates a comment with the given value. - - The value. - A comment with the given value. - - - - Creates a string with the given value. - - The value. - A string with the given value. - - - - Creates a null value. - - A null value. - - - - Creates a undefined value. - - A undefined value. - - - - Gets the node type for this . - - The type. - - - - Gets or sets the underlying token value. - - The underlying token value. - - - - Writes this token to a . - - A into which this method will write. - A collection of s which will be used when writing the token. - - - - Indicates whether the current object is equal to another object of the same type. - - - true if the current object is equal to the parameter; otherwise, false. - - An object to compare with this object. - - - - Determines whether the specified is equal to the current . - - The to compare with the current . - - true if the specified is equal to the current ; otherwise, false. - - - - - Serves as a hash function for a particular type. - - - A hash code for the current . - - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format. - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format provider. - - A that represents this instance. - - - - - Returns a that represents this instance. - - The format. - The format provider. - - A that represents this instance. - - - - - Returns the responsible for binding operations performed on this object. - - The expression tree representation of the runtime value. - - The to bind this object. - - - - - Compares the current instance with another object of the same type and returns an integer that indicates whether the current instance precedes, follows, or occurs in the same position in the sort order as the other object. - - An object to compare with this instance. - - A 32-bit signed integer that indicates the relative order of the objects being compared. The return value has these meanings: - Value - Meaning - Less than zero - This instance is less than . - Zero - This instance is equal to . - Greater than zero - This instance is greater than . - - - is not of the same type as this instance. - - - - - Specifies how line information is handled when loading JSON. - - - - - Ignore line information. - - - - - Load line information. - - - - - Specifies how JSON arrays are merged together. - - - - Concatenate arrays. - - - Union arrays, skipping items that already exist. - - - Replace all array items. - - - Merge array items together, matched by index. - - - - Specifies how null value properties are merged. - - - - - The content's null value properties will be ignored during merging. - - - - - The content's null value properties will be merged. - - - - - Specifies the member serialization options for the . - - - - - All public members are serialized by default. Members can be excluded using or . - This is the default member serialization mode. - - - - - Only members marked with or are serialized. - This member serialization mode can also be set by marking the class with . - - - - - All public and private fields are serialized. Members can be excluded using or . - This member serialization mode can also be set by marking the class with - and setting IgnoreSerializableAttribute on to false. - - - - - Specifies metadata property handling options for the . - - - - - Read metadata properties located at the start of a JSON object. - - - - - Read metadata properties located anywhere in a JSON object. Note that this setting will impact performance. - - - - - Do not try to read metadata properties. - - - - - Specifies missing member handling options for the . - - - - - Ignore a missing member and do not attempt to deserialize it. - - - - - Throw a when a missing member is encountered during deserialization. - - - - - Specifies null value handling options for the . - - - - - - - - - Include null values when serializing and deserializing objects. - - - - - Ignore null values when serializing and deserializing objects. - - - - - Specifies how object creation is handled by the . - - - - - Reuse existing objects, create new objects when needed. - - - - - Only reuse existing objects. - - - - - Always create new objects. - - - - - Specifies reference handling options for the . - Note that references cannot be preserved when a value is set via a non-default constructor such as types that implement . - - - - - - - - Do not preserve references when serializing types. - - - - - Preserve references when serializing into a JSON object structure. - - - - - Preserve references when serializing into a JSON array structure. - - - - - Preserve references when serializing. - - - - - Specifies reference loop handling options for the . - - - - - Throw a when a loop is encountered. - - - - - Ignore loop references and do not serialize. - - - - - Serialize loop references. - - - - - Indicating whether a property is required. - - - - - The property is not required. The default state. - - - - - The property must be defined in JSON but can be a null value. - - - - - The property must be defined in JSON and cannot be a null value. - - - - - The property is not required but it cannot be a null value. - - - - - - Contains the JSON schema extension methods. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - - Determines whether the is valid. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - - true if the specified is valid; otherwise, false. - - - - - - Determines whether the is valid. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - When this method returns, contains any error messages generated while validating. - - true if the specified is valid; otherwise, false. - - - - - - Validates the specified . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - - - - - Validates the specified . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - The source to test. - The schema to test with. - The validation event handler. - - - - - An in-memory representation of a JSON Schema. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets the id. - - - - - Gets or sets the title. - - - - - Gets or sets whether the object is required. - - - - - Gets or sets whether the object is read-only. - - - - - Gets or sets whether the object is visible to users. - - - - - Gets or sets whether the object is transient. - - - - - Gets or sets the description of the object. - - - - - Gets or sets the types of values allowed by the object. - - The type. - - - - Gets or sets the pattern. - - The pattern. - - - - Gets or sets the minimum length. - - The minimum length. - - - - Gets or sets the maximum length. - - The maximum length. - - - - Gets or sets a number that the value should be divisible by. - - A number that the value should be divisible by. - - - - Gets or sets the minimum. - - The minimum. - - - - Gets or sets the maximum. - - The maximum. - - - - Gets or sets a flag indicating whether the value can not equal the number defined by the minimum attribute (). - - A flag indicating whether the value can not equal the number defined by the minimum attribute (). - - - - Gets or sets a flag indicating whether the value can not equal the number defined by the maximum attribute (). - - A flag indicating whether the value can not equal the number defined by the maximum attribute (). - - - - Gets or sets the minimum number of items. - - The minimum number of items. - - - - Gets or sets the maximum number of items. - - The maximum number of items. - - - - Gets or sets the of items. - - The of items. - - - - Gets or sets a value indicating whether items in an array are validated using the instance at their array position from . - - - true if items are validated using their array position; otherwise, false. - - - - - Gets or sets the of additional items. - - The of additional items. - - - - Gets or sets a value indicating whether additional items are allowed. - - - true if additional items are allowed; otherwise, false. - - - - - Gets or sets whether the array items must be unique. - - - - - Gets or sets the of properties. - - The of properties. - - - - Gets or sets the of additional properties. - - The of additional properties. - - - - Gets or sets the pattern properties. - - The pattern properties. - - - - Gets or sets a value indicating whether additional properties are allowed. - - - true if additional properties are allowed; otherwise, false. - - - - - Gets or sets the required property if this property is present. - - The required property if this property is present. - - - - Gets or sets the a collection of valid enum values allowed. - - A collection of valid enum values allowed. - - - - Gets or sets disallowed types. - - The disallowed types. - - - - Gets or sets the default value. - - The default value. - - - - Gets or sets the collection of that this schema extends. - - The collection of that this schema extends. - - - - Gets or sets the format. - - The format. - - - - Initializes a new instance of the class. - - - - - Reads a from the specified . - - The containing the JSON Schema to read. - The object representing the JSON Schema. - - - - Reads a from the specified . - - The containing the JSON Schema to read. - The to use when resolving schema references. - The object representing the JSON Schema. - - - - Load a from a string that contains JSON Schema. - - A that contains JSON Schema. - A populated from the string that contains JSON Schema. - - - - Load a from a string that contains JSON Schema using the specified . - - A that contains JSON Schema. - The resolver. - A populated from the string that contains JSON Schema. - - - - Writes this schema to a . - - A into which this method will write. - - - - Writes this schema to a using the specified . - - A into which this method will write. - The resolver used. - - - - Returns a that represents the current . - - - A that represents the current . - - - - - - Returns detailed information about the schema exception. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets the line number indicating where the error occurred. - - The line number indicating where the error occurred. - - - - Gets the line position indicating where the error occurred. - - The line position indicating where the error occurred. - - - - Gets the path to the JSON where the error occurred. - - The path to the JSON where the error occurred. - - - - Initializes a new instance of the class. - - - - - Initializes a new instance of the class - with a specified error message. - - The error message that explains the reason for the exception. - - - - Initializes a new instance of the class - with a specified error message and a reference to the inner exception that is the cause of this exception. - - The error message that explains the reason for the exception. - The exception that is the cause of the current exception, or null if no inner exception is specified. - - - - Initializes a new instance of the class. - - The that holds the serialized object data about the exception being thrown. - The that contains contextual information about the source or destination. - The parameter is null. - The class name is null or is zero (0). - - - - - Generates a from a specified . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets how undefined schemas are handled by the serializer. - - - - - Gets or sets the contract resolver. - - The contract resolver. - - - - Generate a from the specified type. - - The type to generate a from. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - The used to resolve schema references. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - Specify whether the generated root will be nullable. - A generated from the specified type. - - - - Generate a from the specified type. - - The type to generate a from. - The used to resolve schema references. - Specify whether the generated root will be nullable. - A generated from the specified type. - - - - - Resolves from an id. - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets or sets the loaded schemas. - - The loaded schemas. - - - - Initializes a new instance of the class. - - - - - Gets a for the specified reference. - - The id. - A for the specified reference. - - - - - The value types allowed by the . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - No type specified. - - - - - String type. - - - - - Float type. - - - - - Integer type. - - - - - Boolean type. - - - - - Object type. - - - - - Array type. - - - - - Null type. - - - - - Any type. - - - - - - Specifies undefined schema Id handling options for the . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Do not infer a schema Id. - - - - - Use the .NET type name as the schema Id. - - - - - Use the assembly qualified .NET type name as the schema Id. - - - - - - Returns detailed information related to the . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - Gets the associated with the validation error. - - The JsonSchemaException associated with the validation error. - - - - Gets the path of the JSON location where the validation error occurred. - - The path of the JSON location where the validation error occurred. - - - - Gets the text description corresponding to the validation error. - - The text description. - - - - - Represents the callback method that will handle JSON schema validation events and the . - - - JSON Schema validation has been moved to its own package. See https://www.newtonsoft.com/jsonschema for more details. - - - - - - A camel case naming strategy. - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - A flag indicating whether extension data names should be processed. - - - - - Initializes a new instance of the class. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Resolves member mappings for a type, camel casing property names. - - - - - Initializes a new instance of the class. - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Used by to resolve a for a given . - - - - - Gets a value indicating whether members are being get and set using dynamic code generation. - This value is determined by the runtime permissions available. - - - true if using dynamic code generation; otherwise, false. - - - - - Gets or sets the default members search flags. - - The default members search flags. - - - - Gets or sets a value indicating whether compiler generated members should be serialized. - - - true if serialized compiler generated members; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore the interface when serializing and deserializing types. - - - true if the interface will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore the attribute when serializing and deserializing types. - - - true if the attribute will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore IsSpecified members when serializing and deserializing types. - - - true if the IsSpecified members will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets a value indicating whether to ignore ShouldSerialize members when serializing and deserializing types. - - - true if the ShouldSerialize members will be ignored when serializing and deserializing types; otherwise, false. - - - - - Gets or sets the naming strategy used to resolve how property names and dictionary keys are serialized. - - The naming strategy used to resolve how property names and dictionary keys are serialized. - - - - Initializes a new instance of the class. - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Gets the serializable members for the type. - - The type to get serializable members for. - The serializable members for the type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates the constructor parameters. - - The constructor to create properties for. - The type's member properties. - Properties for the given . - - - - Creates a for the given . - - The matching member property. - The constructor parameter. - A created for the given . - - - - Resolves the default for the contract. - - Type of the object. - The contract's default . - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Creates a for the given type. - - Type of the object. - A for the given type. - - - - Determines which contract type is created for the given type. - - Type of the object. - A for the given type. - - - - Creates properties for the given . - - The type to create properties for. - /// The member serialization mode for the type. - Properties for the given . - - - - Creates the used by the serializer to get and set values from a member. - - The member. - The used by the serializer to get and set values from a member. - - - - Creates a for the given . - - The member's parent . - The member to create a for. - A created for the given . - - - - Resolves the name of the property. - - Name of the property. - Resolved name of the property. - - - - Resolves the name of the extension data. By default no changes are made to extension data names. - - Name of the extension data. - Resolved name of the extension data. - - - - Resolves the key of the dictionary. By default is used to resolve dictionary keys. - - Key of the dictionary. - Resolved key of the dictionary. - - - - Gets the resolved name of the property. - - Name of the property. - Name of the property. - - - - The default naming strategy. Property names and dictionary keys are unchanged. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - The default serialization binder used when resolving and loading classes from type names. - - - - - Initializes a new instance of the class. - - - - - When overridden in a derived class, controls the binding of a serialized object to a type. - - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - The type of the object the formatter creates a new instance of. - - - - - When overridden in a derived class, controls the binding of a serialized object to a type. - - The type of the object the formatter creates a new instance of. - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - - - Represents a trace writer that writes to the application's instances. - - - - - Gets the that will be used to filter the trace messages passed to the writer. - For example a filter level of will exclude messages and include , - and messages. - - - The that will be used to filter the trace messages passed to the writer. - - - - - Writes the specified trace level, message and optional exception. - - The at which to write this trace. - The trace message. - The trace exception. This parameter is optional. - - - - Get and set values for a using dynamic methods. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Provides information surrounding an error. - - - - - Gets the error. - - The error. - - - - Gets the original object that caused the error. - - The original object that caused the error. - - - - Gets the member that caused the error. - - The member that caused the error. - - - - Gets the path of the JSON location where the error occurred. - - The path of the JSON location where the error occurred. - - - - Gets or sets a value indicating whether this is handled. - - true if handled; otherwise, false. - - - - Provides data for the Error event. - - - - - Gets the current object the error event is being raised against. - - The current object the error event is being raised against. - - - - Gets the error context. - - The error context. - - - - Initializes a new instance of the class. - - The current object. - The error context. - - - - Get and set values for a using dynamic methods. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Provides methods to get attributes. - - - - - Returns a collection of all of the attributes, or an empty collection if there are no attributes. - - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. - - The type of the attributes. - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Used by to resolve a for a given . - - - - - - - - - Resolves the contract for a given type. - - The type to resolve a contract for. - The contract for a given type. - - - - Used to resolve references when serializing and deserializing JSON by the . - - - - - Resolves a reference to its object. - - The serialization context. - The reference to resolve. - The object that was resolved from the reference. - - - - Gets the reference for the specified object. - - The serialization context. - The object to get a reference for. - The reference to the object. - - - - Determines whether the specified object is referenced. - - The serialization context. - The object to test for a reference. - - true if the specified object is referenced; otherwise, false. - - - - - Adds a reference to the specified object. - - The serialization context. - The reference. - The object to reference. - - - - Allows users to control class loading and mandate what class to load. - - - - - When implemented, controls the binding of a serialized object to a type. - - Specifies the name of the serialized object. - Specifies the name of the serialized object - The type of the object the formatter creates a new instance of. - - - - When implemented, controls the binding of a serialized object to a type. - - The type of the object the formatter creates a new instance of. - Specifies the name of the serialized object. - Specifies the name of the serialized object. - - - - Represents a trace writer. - - - - - Gets the that will be used to filter the trace messages passed to the writer. - For example a filter level of will exclude messages and include , - and messages. - - The that will be used to filter the trace messages passed to the writer. - - - - Writes the specified trace level, message and optional exception. - - The at which to write this trace. - The trace message. - The trace exception. This parameter is optional. - - - - Provides methods to get and set values. - - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - Contract details for a used by the . - - - - - Gets the of the collection items. - - The of the collection items. - - - - Gets a value indicating whether the collection type is a multidimensional array. - - true if the collection type is a multidimensional array; otherwise, false. - - - - Gets or sets the function used to create the object. When set this function will override . - - The function used to create the object. - - - - Gets a value indicating whether the creator has a parameter with the collection values. - - true if the creator has a parameter with the collection values; otherwise, false. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the default collection items . - - The converter. - - - - Gets or sets a value indicating whether the collection items preserve object references. - - true if collection items preserve object references; otherwise, false. - - - - Gets or sets the collection item reference loop handling. - - The reference loop handling. - - - - Gets or sets the collection item type name handling. - - The type name handling. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Handles serialization callback events. - - The object that raised the callback event. - The streaming context. - - - - Handles serialization error callback events. - - The object that raised the callback event. - The streaming context. - The error context. - - - - Sets extension data for an object during deserialization. - - The object to set extension data on. - The extension data key. - The extension data value. - - - - Gets extension data for an object during serialization. - - The object to set extension data on. - - - - Contract details for a used by the . - - - - - Gets the underlying type for the contract. - - The underlying type for the contract. - - - - Gets or sets the type created during deserialization. - - The type created during deserialization. - - - - Gets or sets whether this type contract is serialized as a reference. - - Whether this type contract is serialized as a reference. - - - - Gets or sets the default for this contract. - - The converter. - - - - Gets the internally resolved for the contract's type. - This converter is used as a fallback converter when no other converter is resolved. - Setting will always override this converter. - - - - - Gets or sets all methods called immediately after deserialization of the object. - - The methods called immediately after deserialization of the object. - - - - Gets or sets all methods called during deserialization of the object. - - The methods called during deserialization of the object. - - - - Gets or sets all methods called after serialization of the object graph. - - The methods called after serialization of the object graph. - - - - Gets or sets all methods called before serialization of the object. - - The methods called before serialization of the object. - - - - Gets or sets all method called when an error is thrown during the serialization of the object. - - The methods called when an error is thrown during the serialization of the object. - - - - Gets or sets the default creator method used to create the object. - - The default creator method used to create the object. - - - - Gets or sets a value indicating whether the default creator is non-public. - - true if the default object creator is non-public; otherwise, false. - - - - Contract details for a used by the . - - - - - Gets or sets the dictionary key resolver. - - The dictionary key resolver. - - - - Gets the of the dictionary keys. - - The of the dictionary keys. - - - - Gets the of the dictionary values. - - The of the dictionary values. - - - - Gets or sets the function used to create the object. When set this function will override . - - The function used to create the object. - - - - Gets a value indicating whether the creator has a parameter with the dictionary values. - - true if the creator has a parameter with the dictionary values; otherwise, false. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets the object's properties. - - The object's properties. - - - - Gets or sets the property name resolver. - - The property name resolver. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the object constructor. - - The object constructor. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Gets or sets the object member serialization. - - The member object serialization. - - - - Gets or sets the missing member handling used when deserializing this object. - - The missing member handling. - - - - Gets or sets a value that indicates whether the object's properties are required. - - - A value indicating whether the object's properties are required. - - - - - Gets or sets how the object's properties with null values are handled during serialization and deserialization. - - How the object's properties with null values are handled during serialization and deserialization. - - - - Gets the object's properties. - - The object's properties. - - - - Gets a collection of instances that define the parameters used with . - - - - - Gets or sets the function used to create the object. When set this function will override . - This function is called with a collection of arguments which are defined by the collection. - - The function used to create the object. - - - - Gets or sets the extension data setter. - - - - - Gets or sets the extension data getter. - - - - - Gets or sets the extension data value type. - - - - - Gets or sets the extension data name resolver. - - The extension data name resolver. - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Maps a JSON property to a .NET member or constructor parameter. - - - - - Gets or sets the name of the property. - - The name of the property. - - - - Gets or sets the type that declared this property. - - The type that declared this property. - - - - Gets or sets the order of serialization of a member. - - The numeric order of serialization. - - - - Gets or sets the name of the underlying member or parameter. - - The name of the underlying member or parameter. - - - - Gets the that will get and set the during serialization. - - The that will get and set the during serialization. - - - - Gets or sets the for this property. - - The for this property. - - - - Gets or sets the type of the property. - - The type of the property. - - - - Gets or sets the for the property. - If set this converter takes precedence over the contract converter for the property type. - - The converter. - - - - Gets or sets the member converter. - - The member converter. - - - - Gets or sets a value indicating whether this is ignored. - - true if ignored; otherwise, false. - - - - Gets or sets a value indicating whether this is readable. - - true if readable; otherwise, false. - - - - Gets or sets a value indicating whether this is writable. - - true if writable; otherwise, false. - - - - Gets or sets a value indicating whether this has a member attribute. - - true if has a member attribute; otherwise, false. - - - - Gets the default value. - - The default value. - - - - Gets or sets a value indicating whether this is required. - - A value indicating whether this is required. - - - - Gets a value indicating whether has a value specified. - - - - - Gets or sets a value indicating whether this property preserves object references. - - - true if this instance is reference; otherwise, false. - - - - - Gets or sets the property null value handling. - - The null value handling. - - - - Gets or sets the property default value handling. - - The default value handling. - - - - Gets or sets the property reference loop handling. - - The reference loop handling. - - - - Gets or sets the property object creation handling. - - The object creation handling. - - - - Gets or sets or sets the type name handling. - - The type name handling. - - - - Gets or sets a predicate used to determine whether the property should be serialized. - - A predicate used to determine whether the property should be serialized. - - - - Gets or sets a predicate used to determine whether the property should be deserialized. - - A predicate used to determine whether the property should be deserialized. - - - - Gets or sets a predicate used to determine whether the property should be serialized. - - A predicate used to determine whether the property should be serialized. - - - - Gets or sets an action used to set whether the property has been deserialized. - - An action used to set whether the property has been deserialized. - - - - Returns a that represents this instance. - - - A that represents this instance. - - - - - Gets or sets the converter used when serializing the property's collection items. - - The collection's items converter. - - - - Gets or sets whether this property's collection items are serialized as a reference. - - Whether this property's collection items are serialized as a reference. - - - - Gets or sets the type name handling used when serializing the property's collection items. - - The collection's items type name handling. - - - - Gets or sets the reference loop handling used when serializing the property's collection items. - - The collection's items reference loop handling. - - - - A collection of objects. - - - - - Initializes a new instance of the class. - - The type. - - - - When implemented in a derived class, extracts the key from the specified element. - - The element from which to extract the key. - The key for the specified element. - - - - Adds a object. - - The property to add to the collection. - - - - Gets the closest matching object. - First attempts to get an exact case match of and then - a case insensitive match. - - Name of the property. - A matching property if found. - - - - Gets a property by property name. - - The name of the property to get. - Type property name string comparison. - A matching property if found. - - - - Contract details for a used by the . - - - - - Initializes a new instance of the class. - - The underlying type for the contract. - - - - Lookup and create an instance of the type described by the argument. - - The type to create. - Optional arguments to pass to an initializing constructor of the JsonConverter. - If null, the default constructor is used. - - - - Represents a trace writer that writes to memory. When the trace message limit is - reached then old trace messages will be removed as new messages are added. - - - - - Gets the that will be used to filter the trace messages passed to the writer. - For example a filter level of will exclude messages and include , - and messages. - - - The that will be used to filter the trace messages passed to the writer. - - - - - Initializes a new instance of the class. - - - - - Writes the specified trace level, message and optional exception. - - The at which to write this trace. - The trace message. - The trace exception. This parameter is optional. - - - - Returns an enumeration of the most recent trace messages. - - An enumeration of the most recent trace messages. - - - - Returns a of the most recent trace messages. - - - A of the most recent trace messages. - - - - - A base class for resolving how property names and dictionary keys are serialized. - - - - - A flag indicating whether dictionary keys should be processed. - Defaults to false. - - - - - A flag indicating whether extension data names should be processed. - Defaults to false. - - - - - A flag indicating whether explicitly specified property names, - e.g. a property name customized with a , should be processed. - Defaults to false. - - - - - Gets the serialized name for a given property name. - - The initial property name. - A flag indicating whether the property has had a name explicitly specified. - The serialized property name. - - - - Gets the serialized name for a given extension data name. - - The initial extension data name. - The serialized extension data name. - - - - Gets the serialized key for a given dictionary key. - - The initial dictionary key. - The serialized dictionary key. - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Hash code calculation - - - - - - Object equality implementation - - - - - - - Compare to another NamingStrategy - - - - - - - Represents a method that constructs an object. - - The object type to create. - - - - When applied to a method, specifies that the method is called when an error occurs serializing an object. - - - - - Provides methods to get attributes from a , , or . - - - - - Initializes a new instance of the class. - - The instance to get attributes for. This parameter should be a , , or . - - - - Returns a collection of all of the attributes, or an empty collection if there are no attributes. - - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Returns a collection of attributes, identified by type, or an empty collection if there are no attributes. - - The type of the attributes. - When true, look up the hierarchy chain for the inherited custom attribute. - A collection of s, or an empty collection. - - - - Get and set values for a using reflection. - - - - - Initializes a new instance of the class. - - The member info. - - - - Sets the value. - - The target to set the value on. - The value to set on the target. - - - - Gets the value. - - The target to get the value from. - The value. - - - - A snake case naming strategy. - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - - - Initializes a new instance of the class. - - - A flag indicating whether dictionary keys should be processed. - - - A flag indicating whether explicitly specified property names should be processed, - e.g. a property name customized with a . - - - A flag indicating whether extension data names should be processed. - - - - - Initializes a new instance of the class. - - - - - Resolves the specified property name. - - The property name to resolve. - The resolved property name. - - - - Specifies how strings are escaped when writing JSON text. - - - - - Only control characters (e.g. newline) are escaped. - - - - - All non-ASCII and control characters (e.g. newline) are escaped. - - - - - HTML (<, >, &, ', ") and control characters (e.g. newline) are escaped. - - - - - Indicates the method that will be used during deserialization for locating and loading assemblies. - - - - - In simple mode, the assembly used during deserialization need not match exactly the assembly used during serialization. Specifically, the version numbers need not match as the LoadWithPartialName method of the class is used to load the assembly. - - - - - In full mode, the assembly used during deserialization must match exactly the assembly used during serialization. The Load method of the class is used to load the assembly. - - - - - Specifies type name handling options for the . - - - should be used with caution when your application deserializes JSON from an external source. - Incoming types should be validated with a custom - when deserializing with a value other than . - - - - - Do not include the .NET type name when serializing types. - - - - - Include the .NET type name when serializing into a JSON object structure. - - - - - Include the .NET type name when serializing into a JSON array structure. - - - - - Always include the .NET type name when serializing. - - - - - Include the .NET type name when the type of the object being serialized is not the same as its declared type. - Note that this doesn't include the root serialized object by default. To include the root object's type name in JSON - you must specify a root type object with - or . - - - - - Determines whether the collection is null or empty. - - The collection. - - true if the collection is null or empty; otherwise, false. - - - - - Adds the elements of the specified collection to the specified generic . - - The list to add to. - The collection of elements to add. - - - - Converts the value to the specified type. If the value is unable to be converted, the - value is checked whether it assignable to the specified type. - - The value to convert. - The culture to use when converting. - The type to convert or cast the value to. - - The converted type. If conversion was unsuccessful, the initial value - is returned if assignable to the target type. - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic that returns a result - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic, but uses one of the arguments for - the result. - - - - - Helper method for generating a MetaObject which calls a - specific method on Dynamic, but uses one of the arguments for - the result. - - - - - Returns a Restrictions object which includes our current restrictions merged - with a restriction limiting our type - - - - - Helper class for serializing immutable collections. - Note that this is used by all builds, even those that don't support immutable collections, in case the DLL is GACed - https://github.com/JamesNK/Newtonsoft.Json/issues/652 - - - - - Gets the type of the typed collection's items. - - The type. - The type of the typed collection's items. - - - - Gets the member's underlying type. - - The member. - The underlying type of the member. - - - - Determines whether the property is an indexed property. - - The property. - - true if the property is an indexed property; otherwise, false. - - - - - Gets the member's value on the object. - - The member. - The target object. - The member's value on the object. - - - - Sets the member's value on the target object. - - The member. - The target. - The value. - - - - Determines whether the specified MemberInfo can be read. - - The MemberInfo to determine whether can be read. - /// if set to true then allow the member to be gotten non-publicly. - - true if the specified MemberInfo can be read; otherwise, false. - - - - - Determines whether the specified MemberInfo can be set. - - The MemberInfo to determine whether can be set. - if set to true then allow the member to be set non-publicly. - if set to true then allow the member to be set if read-only. - - true if the specified MemberInfo can be set; otherwise, false. - - - - - Builds a string. Unlike this class lets you reuse its internal buffer. - - - - - Determines whether the string is all white space. Empty string will return false. - - The string to test whether it is all white space. - - true if the string is all white space; otherwise, false. - - - - - Specifies the state of the . - - - - - An exception has been thrown, which has left the in an invalid state. - You may call the method to put the in the Closed state. - Any other method calls result in an being thrown. - - - - - The method has been called. - - - - - An object is being written. - - - - - An array is being written. - - - - - A constructor is being written. - - - - - A property is being written. - - - - - A write method has not been called. - - - - Specifies that an output will not be null even if the corresponding type allows it. - - - Specifies that when a method returns , the parameter will not be null even if the corresponding type allows it. - - - Initializes the attribute with the specified return value condition. - - The return value condition. If the method returns this value, the associated parameter will not be null. - - - - Gets the return value condition. - - - Specifies that an output may be null even if the corresponding type disallows it. - - - Specifies that null is allowed as an input even if the corresponding type disallows it. - - -