@atelierai/uco 1.0.6 → 1.0.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +32 -0
- package/README.md +7 -8
- package/bin/server.mjs +1 -1
- package/bin/stub-plugin.mjs +3 -3
- package/dist/codegen/coerce.js +1 -1
- package/dist/codegen/coerce.js.map +1 -1
- package/dist/codegen/emit.js +2 -2
- package/dist/codegen/emit.js.map +1 -1
- package/dist/codegen/refs.js +1 -1
- package/dist/codegen/refs.js.map +1 -1
- package/dist/codegen/types.js +1 -1
- package/dist/codegen/types.js.map +1 -1
- package/dist/commands/call.js +1 -1
- package/dist/commands/call.js.map +1 -1
- package/dist/commands/devops/configure.js +1 -1
- package/dist/commands/devops/configure.js.map +1 -1
- package/dist/commands/devops/index.js +0 -4
- package/dist/commands/devops/index.js.map +1 -1
- package/dist/commands/devops/install-plugin.js +1 -1
- package/dist/commands/devops/install-plugin.js.map +1 -1
- package/dist/commands/devops/open.js +7 -7
- package/dist/commands/devops/open.js.map +1 -1
- package/dist/commands/devops/status.js +3 -3
- package/dist/commands/devops/status.js.map +1 -1
- package/dist/commands/exec.js +1 -1
- package/dist/commands/exec.js.map +1 -1
- package/dist/commands/install.js +2 -2
- package/dist/commands/install.js.map +1 -1
- package/dist/commands/prompt.js +2 -2
- package/dist/commands/prompt.js.map +1 -1
- package/dist/commands/resource.js +2 -2
- package/dist/commands/resource.js.map +1 -1
- package/dist/commands/update.js +1 -26
- package/dist/commands/update.js.map +1 -1
- package/dist/config/port.js +2 -2
- package/dist/config/port.js.map +1 -1
- package/dist/config/resolve.d.ts +1 -2
- package/dist/config/resolve.js +0 -12
- package/dist/config/resolve.js.map +1 -1
- package/dist/devops/lib/configure.d.ts +1 -1
- package/dist/devops/lib/configure.js +2 -2
- package/dist/devops/lib/configure.js.map +1 -1
- package/dist/devops/lib/install-plugin.d.ts +1 -1
- package/dist/devops/lib/install-plugin.js +1 -1
- package/dist/devops/lib/install-plugin.js.map +1 -1
- package/dist/devops/lib/install.d.ts +2 -2
- package/dist/devops/lib/install.js +5 -5
- package/dist/devops/lib/install.js.map +1 -1
- package/dist/devops/lib/open.js +11 -32
- package/dist/devops/lib/open.js.map +1 -1
- package/dist/devops/lib/remove-plugin.d.ts +1 -1
- package/dist/devops/lib/remove-plugin.js +3 -3
- package/dist/devops/lib/remove-plugin.js.map +1 -1
- package/dist/devops/lib/run-tool.d.ts +2 -2
- package/dist/devops/lib/run-tool.js +2 -2
- package/dist/devops/lib/types.d.ts +17 -54
- package/dist/devops/lib/types.js +2 -2
- package/dist/devops/lib/types.js.map +1 -1
- package/dist/devops/lib/update.d.ts +0 -15
- package/dist/devops/lib/update.js +2 -91
- package/dist/devops/lib/update.js.map +1 -1
- package/dist/devops/utils/agents.d.ts +0 -28
- package/dist/devops/utils/agents.js +0 -462
- package/dist/devops/utils/agents.js.map +1 -1
- package/dist/devops/utils/config.d.ts +9 -18
- package/dist/devops/utils/config.js +7 -20
- package/dist/devops/utils/config.js.map +1 -1
- package/dist/devops/utils/editor-cache.js +1 -1
- package/dist/devops/utils/editor-cache.js.map +1 -1
- package/dist/devops/utils/launch-error-dismiss.d.ts +2 -2
- package/dist/devops/utils/launch-error-dismiss.js +5 -5
- package/dist/devops/utils/manifest.d.ts +2 -2
- package/dist/devops/utils/manifest.js +3 -3
- package/dist/devops/utils/manifest.js.map +1 -1
- package/dist/devops/utils/port.d.ts +1 -1
- package/dist/devops/utils/port.js +1 -1
- package/dist/devops/utils/probe.d.ts +1 -1
- package/dist/devops/utils/probe.js +1 -1
- package/dist/devops/utils/ui.js +2 -2
- package/dist/devops/utils/ui.js.map +1 -1
- package/dist/devops/utils/unity-editor.js +4 -4
- package/dist/devops/utils/unity-editor.js.map +1 -1
- package/dist/devops/utils/unity-hub.js +1 -1
- package/dist/devops/utils/unity-hub.js.map +1 -1
- package/dist/devops/utils/unity-shutdown.d.ts +1 -1
- package/dist/devops/utils/unity-shutdown.js +1 -1
- package/dist/devops/utils/update-check.js +3 -3
- package/dist/devops/utils/update-check.js.map +1 -1
- package/dist/devops/utils/vendor.d.ts +2 -2
- package/dist/devops/utils/vendor.js +4 -4
- package/dist/devops/utils/vendor.js.map +1 -1
- package/dist/generated/tools.js +1 -1
- package/dist/generated/tools.js.map +1 -1
- package/dist/program.js +3 -3
- package/dist/program.js.map +1 -1
- package/dist/server/app.d.ts +5 -5
- package/dist/server/app.js +3 -3
- package/dist/server/app.js.map +1 -1
- package/dist/server/index.d.ts +2 -2
- package/dist/server/index.js +2 -2
- package/dist/server/index.js.map +1 -1
- package/dist/server/rest/auth.d.ts +1 -1
- package/dist/server/rest/auth.js +3 -3
- package/dist/server/rest/auth.js.map +1 -1
- package/dist/server/rest/context.d.ts +2 -2
- package/dist/server/rest/forward.d.ts +2 -2
- package/dist/server/session/store.d.ts +3 -3
- package/dist/server/session/store.js +4 -4
- package/dist/server/session/store.js.map +1 -1
- package/dist/server/stub-client/scenarios.js +8 -8
- package/dist/server/stub-client/scenarios.js.map +1 -1
- package/dist/server/types.d.ts +11 -11
- package/dist/server/types.js +9 -9
- package/dist/server/types.js.map +1 -1
- package/dist/server/ws/hub.d.ts +8 -8
- package/dist/server/ws/hub.js +13 -13
- package/dist/server/ws/hub.js.map +1 -1
- package/dist/server/ws/rpc.d.ts +3 -3
- package/dist/server/ws/rpc.js +4 -4
- package/dist/server/ws/rpc.js.map +1 -1
- package/dist/transport/index.d.ts +1 -1
- package/dist/transport/index.js +3 -3
- package/dist/transport/index.js.map +1 -1
- package/dist/transport/rest.js +1 -1
- package/dist/transport/rest.js.map +1 -1
- package/docs/INSTALL.zh-CN.md +2 -3
- package/package.json +1 -1
- package/skills/uco-setup/SKILL.md +1 -1
- package/skills/unity-editor/references/diagnostics.md +24 -24
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetConfig.cs +132 -132
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDependencyResolver.cs +237 -237
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/DependencyResolver/NuGetDownloader.cs +82 -82
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/MenuItems.cs +247 -247
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Resource/ToolGroups.cs +153 -153
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/SystemTool/Skills.Create.SkillBody.cs +155 -155
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Console.GetLogs.cs +95 -95
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Docs.HttpFetcher.cs +139 -139
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Instance.ListAll.cs +72 -72
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/PathReadHelper.cs +106 -106
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Script.Validate.cs +252 -252
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests/TestResultCollector.cs +537 -537
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/API/Tool/Tests.cs +400 -400
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Branding/ProductInfo.cs +62 -62
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/CopilotServerManager.cs +1034 -1034
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Skills/Skill_InitialSetup.cs +197 -197
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.Editor.cs +201 -201
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Startup.cs +67 -67
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Components/ExtensionPanel.cs +379 -379
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/MenuItems.cs +72 -72
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/UnityCopilotProjectSettingsProvider.cs +64 -64
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotListWindowBase.cs +393 -393
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/CopilotWindowBase.cs +113 -113
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.Connection.cs +3 -3
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotFeatures.cs +195 -195
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CopilotServer.cs +372 -372
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.CreateGUI.cs +354 -354
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowEditor.cs +78 -78
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/MainWindowInitializer.cs +47 -47
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UI/Window/SerializationCheckWindow.cs +190 -190
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Build.cs +91 -91
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Config.cs +298 -298
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.Static.cs +296 -296
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/UnityCopilotPluginEditor.cs +69 -69
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PackageUtils.cs +168 -168
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/PrefsKeyMigration.cs +119 -119
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/SafeDefaultsGuard.cs +180 -180
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ScriptUtils.cs +229 -229
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/ToolsManifestGenerator.cs +200 -200
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringAdapters.cs +1467 -1467
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityAuthoringSafetyReloadGuard.cs +65 -65
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/Scripts/Utils/UnityCopilotUpdateProjectSettings.cs +66 -66
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/ReflectorNet.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.Common.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Plugins/Uco.Framework.dll +0 -0
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Converter/Json/Structures/EntityIdConverter.cs +120 -120
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.cs +110 -110
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Data/GameObjectMetadata.pre-Unity.6.5.cs +110 -110
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Unity/Logs/FileLogStorage.cs +547 -547
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Build.cs +243 -243
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.Config.cs +175 -175
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPlugin.cs +295 -295
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginBuilder.cs +105 -105
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.Static.cs +155 -155
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/UnityCopilotPluginRuntime.cs +59 -59
- package/vendor/plugin/com.atelierai.unity.copilot/Runtime/Utils/EnvironmentUtils.cs +368 -368
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/AtomicApiTestFixtures.cs +99 -99
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AtomicApi/TryModifyAtTests.cs +227 -227
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/AuthoringSafetyPilotTests.cs +1050 -1050
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +304 -304
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +4 -4
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/BlacklistTypeTests.cs +416 -416
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.cs +172 -172
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataFieldScriptableObjectPopulationTests.pre-Unity.6.5.cs +169 -169
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.cs +182 -182
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPopulationTests.pre-Unity.6.5.cs +179 -179
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.cs +175 -175
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyPopulationTests.pre-Unity.6.5.cs +174 -174
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.cs +171 -171
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ReflectionConverter/DataPropertyScriptableObjectPopulationTests.pre-Unity.6.5.cs +170 -170
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsCreateFolderTests.cs +243 -243
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsMaterialTests.cs +101 -101
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.cs +348 -348
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsPrefabCreateTests.pre-Unity.6.5.cs +348 -348
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Assets/AssetsShaderTests.cs +194 -194
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.cs +573 -573
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/PathBasedToolTests.pre-Unity.6.5.cs +513 -513
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.cs +154 -154
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotCameraTests.pre-Unity.6.5.cs +154 -154
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotGameViewTests.cs +164 -164
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/Screenshot/ScreenshotSceneViewTests.cs +155 -155
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolParameterTests.cs +56 -56
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.cs +651 -651
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.pre-Unity.6.5.cs +651 -651
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/ToolBatchExecutionIntegrationTests.cs +407 -407
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs → UcoPluginTests.cs} +105 -105
- package/vendor/plugin/com.atelierai.unity.copilot/package.json +1 -1
- package/dist/commands/devops/login.d.ts +0 -2
- package/dist/commands/devops/login.js +0 -29
- package/dist/commands/devops/login.js.map +0 -1
- package/dist/commands/devops/setup-mcp.d.ts +0 -2
- package/dist/commands/devops/setup-mcp.js +0 -41
- package/dist/commands/devops/setup-mcp.js.map +0 -1
- package/dist/devops/commands-upstream/bootstrap-local.d.ts +0 -17
- package/dist/devops/commands-upstream/bootstrap-local.js +0 -87
- package/dist/devops/commands-upstream/bootstrap-local.js.map +0 -1
- package/dist/devops/commands-upstream/close.d.ts +0 -57
- package/dist/devops/commands-upstream/close.js +0 -188
- package/dist/devops/commands-upstream/close.js.map +0 -1
- package/dist/devops/commands-upstream/configure.d.ts +0 -2
- package/dist/devops/commands-upstream/configure.js +0 -87
- package/dist/devops/commands-upstream/configure.js.map +0 -1
- package/dist/devops/commands-upstream/create-project.d.ts +0 -2
- package/dist/devops/commands-upstream/create-project.js +0 -43
- package/dist/devops/commands-upstream/create-project.js.map +0 -1
- package/dist/devops/commands-upstream/install-plugin.d.ts +0 -2
- package/dist/devops/commands-upstream/install-plugin.js +0 -64
- package/dist/devops/commands-upstream/install-plugin.js.map +0 -1
- package/dist/devops/commands-upstream/install-unity.d.ts +0 -2
- package/dist/devops/commands-upstream/install-unity.js +0 -72
- package/dist/devops/commands-upstream/install-unity.js.map +0 -1
- package/dist/devops/commands-upstream/login.d.ts +0 -2
- package/dist/devops/commands-upstream/login.js +0 -33
- package/dist/devops/commands-upstream/login.js.map +0 -1
- package/dist/devops/commands-upstream/open.d.ts +0 -32
- package/dist/devops/commands-upstream/open.js +0 -282
- package/dist/devops/commands-upstream/open.js.map +0 -1
- package/dist/devops/commands-upstream/remove-plugin.d.ts +0 -2
- package/dist/devops/commands-upstream/remove-plugin.js +0 -41
- package/dist/devops/commands-upstream/remove-plugin.js.map +0 -1
- package/dist/devops/commands-upstream/run-system-tool.d.ts +0 -1
- package/dist/devops/commands-upstream/run-system-tool.js +0 -13
- package/dist/devops/commands-upstream/run-system-tool.js.map +0 -1
- package/dist/devops/commands-upstream/run-tool-builder.d.ts +0 -25
- package/dist/devops/commands-upstream/run-tool-builder.js +0 -125
- package/dist/devops/commands-upstream/run-tool-builder.js.map +0 -1
- package/dist/devops/commands-upstream/run-tool.d.ts +0 -1
- package/dist/devops/commands-upstream/run-tool.js +0 -13
- package/dist/devops/commands-upstream/run-tool.js.map +0 -1
- package/dist/devops/commands-upstream/setup-mcp.d.ts +0 -2
- package/dist/devops/commands-upstream/setup-mcp.js +0 -68
- package/dist/devops/commands-upstream/setup-mcp.js.map +0 -1
- package/dist/devops/commands-upstream/setup-skills.d.ts +0 -2
- package/dist/devops/commands-upstream/setup-skills.js +0 -167
- package/dist/devops/commands-upstream/setup-skills.js.map +0 -1
- package/dist/devops/commands-upstream/status.d.ts +0 -2
- package/dist/devops/commands-upstream/status.js +0 -87
- package/dist/devops/commands-upstream/status.js.map +0 -1
- package/dist/devops/commands-upstream/update.d.ts +0 -2
- package/dist/devops/commands-upstream/update.js +0 -77
- package/dist/devops/commands-upstream/update.js.map +0 -1
- package/dist/devops/commands-upstream/wait-for-ready.d.ts +0 -2
- package/dist/devops/commands-upstream/wait-for-ready.js +0 -93
- package/dist/devops/commands-upstream/wait-for-ready.js.map +0 -1
- package/dist/devops/lib/setup-mcp.d.ts +0 -35
- package/dist/devops/lib/setup-mcp.js +0 -177
- package/dist/devops/lib/setup-mcp.js.map +0 -1
- package/dist/devops/utils/cloud-login.d.ts +0 -7
- package/dist/devops/utils/cloud-login.js +0 -56
- package/dist/devops/utils/cloud-login.js.map +0 -1
- /package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs.meta → UcoPluginTests.cs.meta} +0 -0
package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Tool/ToolThreadSafetyTests.cs
CHANGED
|
@@ -1,651 +1,651 @@
|
|
|
1
|
-
/*
|
|
2
|
-
┌──────────────────────────────────────────────────────────────────┐
|
|
3
|
-
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
|
|
4
|
-
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
|
|
5
|
-
│ Copyright (c) 2025 Ivan Murzak │
|
|
6
|
-
│ Licensed under the Apache License, Version 2.0. │
|
|
7
|
-
│ See the LICENSE file in the project root for more information. │
|
|
8
|
-
└──────────────────────────────────────────────────────────────────┘
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
#nullable enable
|
|
12
|
-
#if UNITY_6000_5_OR_NEWER
|
|
13
|
-
using System.Collections;
|
|
14
|
-
using System.IO;
|
|
15
|
-
using com.AtelierAI.Unity.Copilot.Editor.API;
|
|
16
|
-
using NUnit.Framework;
|
|
17
|
-
using UnityEditor;
|
|
18
|
-
using UnityEngine;
|
|
19
|
-
using UnityEngine.TestTools;
|
|
20
|
-
|
|
21
|
-
namespace com.AtelierAI.Unity.Copilot.Editor.Tests
|
|
22
|
-
{
|
|
23
|
-
/// <summary>
|
|
24
|
-
/// Thread-safety coverage for
|
|
25
|
-
/// <see href="https://github.com/IvanMurzak/uco-plugin/issues/633">#633</see>.
|
|
26
|
-
///
|
|
27
|
-
/// For each tool we want to guarantee the tool's body handles invocation from any
|
|
28
|
-
/// thread (the
|
|
29
|
-
/// so tools that touch Unity APIs must marshal back to the main thread via
|
|
30
|
-
/// <c>MainThread.Instance.Run</c>). Forgetting to do so caused the regression in
|
|
31
|
-
/// <see href="https://github.com/IvanMurzak/uco-plugin/issues/632">#632</see> /
|
|
32
|
-
/// <see href="https://github.com/IvanMurzak/uco-plugin/pull/637">#637</see>
|
|
33
|
-
/// (<c>console-clear-logs</c>).
|
|
34
|
-
///
|
|
35
|
-
/// <b>Excluded tools</b>: <c>package-add</c>/<c>package-remove</c> (trigger domain
|
|
36
|
-
/// reload), <c>tests-run</c> (recursive hang), system tools (<c>ping</c>,
|
|
37
|
-
/// <c>unity-skill-*</c>). See inline comments for rationale.
|
|
38
|
-
///
|
|
39
|
-
/// Two helpers from <see cref="BaseTest"/> are used:
|
|
40
|
-
/// <list type="bullet">
|
|
41
|
-
/// <item><see cref="BaseTest.RunToolBothThreads"/> — happy path; asserts the tool
|
|
42
|
-
/// succeeds on both threads. Used when crafting a valid input is practical.</item>
|
|
43
|
-
/// <item><see cref="BaseTest.RunToolExpectNoThreadViolation"/> — lenient mode; tolerates
|
|
44
|
-
/// business-level errors and only fails the test if Unity surfaces a
|
|
45
|
-
/// <c>"can only be called from the main thread"</c> exception. Used for tools
|
|
46
|
-
/// that need elaborate setup (<c>SerializedMember</c>, prefab stages, etc.) or
|
|
47
|
-
/// whose happy path would mutate the project in risky ways
|
|
48
|
-
/// (<c>package-add</c>/<c>package-remove</c>, <c>tests-run</c>, etc.).</item>
|
|
49
|
-
/// </list>
|
|
50
|
-
/// </summary>
|
|
51
|
-
public class ToolThreadSafetyTests : BaseTest
|
|
52
|
-
{
|
|
53
|
-
const string TmpFolderName = "BgThreadTests_TMP";
|
|
54
|
-
const string TmpFolder = "Assets/" + TmpFolderName;
|
|
55
|
-
|
|
56
|
-
[TearDown]
|
|
57
|
-
public void BgTearDown()
|
|
58
|
-
{
|
|
59
|
-
if (AssetDatabase.IsValidFolder(TmpFolder))
|
|
60
|
-
AssetDatabase.DeleteAsset(TmpFolder);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
// ================================================================
|
|
64
|
-
// NO-ARG / READ-ONLY tools (happy path, empty JSON)
|
|
65
|
-
// ================================================================
|
|
66
|
-
|
|
67
|
-
[UnityTest] public IEnumerator ToolList_BothThreads()
|
|
68
|
-
=> RunToolBothThreads(Tool_Tool.ToolListId, "{}");
|
|
69
|
-
|
|
70
|
-
[UnityTest] public IEnumerator GameObjectComponentListAll_BothThreads()
|
|
71
|
-
=> RunToolBothThreads(Tool_GameObject.ComponentListToolId, @"{""pageSize"":3}");
|
|
72
|
-
|
|
73
|
-
[UnityTest] public IEnumerator SceneListOpened_BothThreads()
|
|
74
|
-
=> RunToolBothThreads(Tool_Scene.SceneListOpenedToolId, "{}");
|
|
75
|
-
|
|
76
|
-
[UnityTest] public IEnumerator SceneGetData_BothThreads()
|
|
77
|
-
=> RunToolBothThreads(Tool_Scene.SceneGetDataToolId, "{}");
|
|
78
|
-
|
|
79
|
-
[UnityTest] public IEnumerator EditorApplicationGetState_BothThreads()
|
|
80
|
-
=> RunToolBothThreads(Tool_Editor.EditorApplicationGetStateToolId, "{}");
|
|
81
|
-
|
|
82
|
-
[UnityTest] public IEnumerator EditorSelectionGet_BothThreads()
|
|
83
|
-
=> RunToolBothThreads(Tool_Editor_Selection.EditorSelectionGetToolId, "{}");
|
|
84
|
-
|
|
85
|
-
[UnityTest] public IEnumerator ConsoleGetLogs_BothThreads()
|
|
86
|
-
=> RunToolBothThreads(Tool_Console.ConsoleGetLogsToolId, "{}");
|
|
87
|
-
|
|
88
|
-
[UnityTest]
|
|
89
|
-
public IEnumerator ConsoleClearLogs_BothThreads()
|
|
90
|
-
{
|
|
91
|
-
// Regression for #632/#637: ClearLogs calls Debug.ClearDeveloperConsole() which
|
|
92
|
-
// MUST be invoked on the main thread.
|
|
93
|
-
yield return RunToolBothThreads(Tool_Console.ConsoleClearLogsToolId, "{}");
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
[UnityTest] public IEnumerator AssetsFind_BothThreads()
|
|
97
|
-
=> RunToolBothThreads(Tool_Assets.AssetsFindToolId, @"{""filter"":""t:Scene"",""maxResults"":3}");
|
|
98
|
-
|
|
99
|
-
[UnityTest] public IEnumerator AssetsFindBuiltIn_BothThreads()
|
|
100
|
-
=> RunToolBothThreads(Tool_Assets.AssetsFindBuiltInToolId, @"{""maxResults"":3}");
|
|
101
|
-
|
|
102
|
-
[UnityTest] public IEnumerator AssetsShaderListAll_BothThreads()
|
|
103
|
-
=> RunToolBothThreads(Tool_Assets_Shader.AssetsShaderListAllToolId, "{}");
|
|
104
|
-
|
|
105
|
-
[UnityTest] public IEnumerator AssetsRefresh_BothThreads()
|
|
106
|
-
=> RunToolBothThreads(Tool_Assets.AssetsRefreshToolId, "{}");
|
|
107
|
-
|
|
108
|
-
[UnityTest] public IEnumerator PackageList_BothThreads()
|
|
109
|
-
=> RunToolBothThreads(Tool_Package.PackageListToolId, "{}");
|
|
110
|
-
|
|
111
|
-
[UnityTest] public IEnumerator TypeGetJsonSchema_BothThreads()
|
|
112
|
-
=> RunToolBothThreads(Tool_Type.TypeGetJsonSchemaToolId, @"{""typeName"":""UnityEngine.Vector3""}");
|
|
113
|
-
|
|
114
|
-
// ================================================================
|
|
115
|
-
// GAMEOBJECT tools — happy path, per-call setup
|
|
116
|
-
// ================================================================
|
|
117
|
-
|
|
118
|
-
[UnityTest]
|
|
119
|
-
public IEnumerator GameObjectCreate_BothThreads()
|
|
120
|
-
{
|
|
121
|
-
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_main""}");
|
|
122
|
-
Assert.IsNotNull(GameObject.Find("bg_main"));
|
|
123
|
-
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_bg""}");
|
|
124
|
-
Assert.IsNotNull(GameObject.Find("bg_bg"));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
[UnityTest]
|
|
128
|
-
public IEnumerator GameObjectFind_BothThreads()
|
|
129
|
-
{
|
|
130
|
-
var go = new GameObject("bg_find");
|
|
131
|
-
var id = go.GetEntityId();
|
|
132
|
-
yield return RunToolBothThreads(Tool_GameObject.GameObjectFindToolId,
|
|
133
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{id}}}}}");
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
[UnityTest]
|
|
137
|
-
public IEnumerator GameObjectDestroy_BothThreads()
|
|
138
|
-
{
|
|
139
|
-
var a = new GameObject("bg_destroy_a").GetEntityId();
|
|
140
|
-
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDestroyToolId,
|
|
141
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{a}}}}}");
|
|
142
|
-
|
|
143
|
-
var b = new GameObject("bg_destroy_b").GetEntityId();
|
|
144
|
-
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDestroyToolId,
|
|
145
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{b}}}}}");
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
[UnityTest]
|
|
149
|
-
public IEnumerator GameObjectDuplicate_BothThreads()
|
|
150
|
-
{
|
|
151
|
-
var a = new GameObject("bg_dup_a").GetEntityId();
|
|
152
|
-
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDuplicateToolId,
|
|
153
|
-
$@"{{""gameObjectRefs"":[{{""instanceID"":{a}}}]}}");
|
|
154
|
-
|
|
155
|
-
var b = new GameObject("bg_dup_b").GetEntityId();
|
|
156
|
-
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDuplicateToolId,
|
|
157
|
-
$@"{{""gameObjectRefs"":[{{""instanceID"":{b}}}]}}");
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
[UnityTest]
|
|
161
|
-
public IEnumerator GameObjectSetParent_BothThreads()
|
|
162
|
-
{
|
|
163
|
-
var parent = new GameObject("bg_parent").GetEntityId();
|
|
164
|
-
var child1 = new GameObject("bg_child_a").GetEntityId();
|
|
165
|
-
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectSetParentToolId,
|
|
166
|
-
$@"{{""gameObjectRefs"":[{{""instanceID"":{child1}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
|
|
167
|
-
|
|
168
|
-
var child2 = new GameObject("bg_child_b").GetEntityId();
|
|
169
|
-
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectSetParentToolId,
|
|
170
|
-
$@"{{""gameObjectRefs"":[{{""instanceID"":{child2}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
[UnityTest]
|
|
174
|
-
public IEnumerator GameObjectComponentAdd_BothThreads()
|
|
175
|
-
{
|
|
176
|
-
var a = new GameObject("bg_addc_a").GetEntityId();
|
|
177
|
-
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentAddToolId,
|
|
178
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{a}}},""componentNames"":[""UnityEngine.BoxCollider""]}}");
|
|
179
|
-
|
|
180
|
-
var b = new GameObject("bg_addc_b").GetEntityId();
|
|
181
|
-
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentAddToolId,
|
|
182
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{b}}},""componentNames"":[""UnityEngine.SphereCollider""]}}");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
[UnityTest]
|
|
186
|
-
public IEnumerator GameObjectComponentGet_BothThreads()
|
|
187
|
-
{
|
|
188
|
-
var go = new GameObject("bg_getc");
|
|
189
|
-
var id = go.GetEntityId();
|
|
190
|
-
// Transform lives at component index 0 on every GameObject.
|
|
191
|
-
yield return RunToolBothThreads(Tool_GameObject.GameObjectComponentGetToolId,
|
|
192
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""index"":0}}}}");
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
[UnityTest]
|
|
196
|
-
public IEnumerator GameObjectComponentDestroy_BothThreads()
|
|
197
|
-
{
|
|
198
|
-
var a = new GameObject("bg_destc_a");
|
|
199
|
-
a.AddComponent<BoxCollider>();
|
|
200
|
-
var idA = a.GetEntityId();
|
|
201
|
-
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentDestroyToolId,
|
|
202
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{idA}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.BoxCollider""}}]}}");
|
|
203
|
-
|
|
204
|
-
var b = new GameObject("bg_destc_b");
|
|
205
|
-
b.AddComponent<SphereCollider>();
|
|
206
|
-
var idB = b.GetEntityId();
|
|
207
|
-
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentDestroyToolId,
|
|
208
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{idB}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.SphereCollider""}}]}}");
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
[UnityTest]
|
|
212
|
-
public IEnumerator GameObjectModify_ThreadSafetyOnly()
|
|
213
|
-
{
|
|
214
|
-
// gameobject-modify requires a SerializedMember tree — we cover thread safety
|
|
215
|
-
// only (business errors from an empty diff are tolerated).
|
|
216
|
-
var id = new GameObject("bg_modify").GetEntityId();
|
|
217
|
-
yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectModifyToolId,
|
|
218
|
-
$@"{{""gameObjectRefs"":[{{""instanceID"":{id}}}],""gameObjectDiffs"":[{{""name"":""root""}}]}}");
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
[UnityTest]
|
|
222
|
-
public IEnumerator GameObjectComponentModify_ThreadSafetyOnly()
|
|
223
|
-
{
|
|
224
|
-
var go = new GameObject("bg_cmodify");
|
|
225
|
-
go.AddComponent<BoxCollider>();
|
|
226
|
-
var id = go.GetEntityId();
|
|
227
|
-
yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectComponentModifyToolId,
|
|
228
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""typeName"":""UnityEngine.BoxCollider""}},""componentDiff"":{{""name"":""root""}}}}");
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// ================================================================
|
|
232
|
-
// OBJECT tools
|
|
233
|
-
// ================================================================
|
|
234
|
-
|
|
235
|
-
[UnityTest]
|
|
236
|
-
public IEnumerator ObjectGetData_BothThreads()
|
|
237
|
-
{
|
|
238
|
-
var id = new GameObject("bg_obj").GetEntityId();
|
|
239
|
-
yield return RunToolBothThreads(Tool_Object.ObjectGetDataToolId,
|
|
240
|
-
$@"{{""objectRef"":{{""instanceID"":{id}}}}}");
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
[UnityTest]
|
|
244
|
-
public IEnumerator ObjectModify_ThreadSafetyOnly()
|
|
245
|
-
{
|
|
246
|
-
var id = new GameObject("bg_objmod").GetEntityId();
|
|
247
|
-
yield return RunToolExpectNoThreadViolation(Tool_Object.ObjectModifyToolId,
|
|
248
|
-
$@"{{""objectRef"":{{""instanceID"":{id}}},""objectDiff"":{{""name"":""root""}}}}");
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
// ================================================================
|
|
252
|
-
// EDITOR tools
|
|
253
|
-
// ================================================================
|
|
254
|
-
|
|
255
|
-
[UnityTest]
|
|
256
|
-
public IEnumerator EditorSelectionSet_BothThreads()
|
|
257
|
-
{
|
|
258
|
-
var id = new GameObject("bg_select").GetEntityId();
|
|
259
|
-
yield return RunToolBothThreads(Tool_Editor_Selection.EditorSelectionSetToolId,
|
|
260
|
-
$@"{{""select"":[{{""instanceID"":{id}}}]}}");
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
[UnityTest]
|
|
264
|
-
public IEnumerator EditorApplicationSetState_BothThreads()
|
|
265
|
-
{
|
|
266
|
-
// No-op toggle: current state is not-playing, not-paused.
|
|
267
|
-
yield return RunToolBothThreads(Tool_Editor.EditorApplicationSetStateToolId,
|
|
268
|
-
@"{""isPlaying"":false,""isPaused"":false}");
|
|
269
|
-
}
|
|
270
|
-
|
|
271
|
-
// ================================================================
|
|
272
|
-
// ASSETS tools
|
|
273
|
-
// ================================================================
|
|
274
|
-
|
|
275
|
-
[UnityTest]
|
|
276
|
-
public IEnumerator AssetsCreateFolder_BothThreads()
|
|
277
|
-
{
|
|
278
|
-
EnsureTmpFolder();
|
|
279
|
-
yield return RunToolMainThreadCoop(Tool_Assets.AssetsCreateFolderToolId,
|
|
280
|
-
$@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""a""}}]}}");
|
|
281
|
-
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCreateFolderToolId,
|
|
282
|
-
$@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""b""}}]}}");
|
|
283
|
-
}
|
|
284
|
-
|
|
285
|
-
[UnityTest]
|
|
286
|
-
public IEnumerator AssetsGetData_BothThreads()
|
|
287
|
-
{
|
|
288
|
-
var assetPath = CreateTmpTextAsset("get_data.txt", "hello");
|
|
289
|
-
yield return RunToolBothThreads(Tool_Assets.AssetsGetDataToolId,
|
|
290
|
-
$@"{{""assetRef"":{{""assetPath"":""{assetPath}""}}}}");
|
|
291
|
-
}
|
|
292
|
-
|
|
293
|
-
[UnityTest]
|
|
294
|
-
public IEnumerator AssetsModify_ThreadSafetyOnly()
|
|
295
|
-
{
|
|
296
|
-
var assetPath = CreateTmpTextAsset("modify.txt", "hello");
|
|
297
|
-
yield return RunToolExpectNoThreadViolation(Tool_Assets.AssetsModifyToolId,
|
|
298
|
-
$@"{{""assetRef"":{{""assetPath"":""{assetPath}""}},""content"":{{""name"":""root""}}}}");
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
[UnityTest]
|
|
302
|
-
public IEnumerator AssetsMaterialCreate_BothThreads()
|
|
303
|
-
{
|
|
304
|
-
EnsureTmpFolder();
|
|
305
|
-
yield return RunToolMainThreadCoop(Tool_Assets.AssetsMaterialCreateToolId,
|
|
306
|
-
$@"{{""assetPath"":""{TmpFolder}/mat_main.mat"",""shaderName"":""Standard""}}");
|
|
307
|
-
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMaterialCreateToolId,
|
|
308
|
-
$@"{{""assetPath"":""{TmpFolder}/mat_bg.mat"",""shaderName"":""Standard""}}");
|
|
309
|
-
}
|
|
310
|
-
|
|
311
|
-
[UnityTest]
|
|
312
|
-
public IEnumerator AssetsShaderGetData_BothThreads()
|
|
313
|
-
{
|
|
314
|
-
// Find any shader asset in the project to query.
|
|
315
|
-
var shaderPath = FindFirstShaderPath();
|
|
316
|
-
if (shaderPath == null)
|
|
317
|
-
{
|
|
318
|
-
Assert.Ignore("No shader asset available in project.");
|
|
319
|
-
yield break;
|
|
320
|
-
}
|
|
321
|
-
yield return RunToolBothThreads(Tool_Assets_Shader.AssetsShaderGetDataToolId,
|
|
322
|
-
$@"{{""assetRef"":{{""assetPath"":""{shaderPath}""}}}}");
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
[UnityTest]
|
|
326
|
-
public IEnumerator AssetsCopy_BothThreads()
|
|
327
|
-
{
|
|
328
|
-
var src = CreateTmpTextAsset("copy_src.txt", "source");
|
|
329
|
-
yield return RunToolMainThreadCoop(Tool_Assets.AssetsCopyToolId,
|
|
330
|
-
$@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_main.txt""]}}");
|
|
331
|
-
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCopyToolId,
|
|
332
|
-
$@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_bg.txt""]}}");
|
|
333
|
-
}
|
|
334
|
-
|
|
335
|
-
[UnityTest]
|
|
336
|
-
public IEnumerator AssetsMove_BothThreads()
|
|
337
|
-
{
|
|
338
|
-
var src1 = CreateTmpTextAsset("move_a.txt", "a");
|
|
339
|
-
yield return RunToolMainThreadCoop(Tool_Assets.AssetsMoveToolId,
|
|
340
|
-
$@"{{""sourcePaths"":[""{src1}""],""destinationPaths"":[""{TmpFolder}/move_a_done.txt""]}}");
|
|
341
|
-
|
|
342
|
-
var src2 = CreateTmpTextAsset("move_b.txt", "b");
|
|
343
|
-
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMoveToolId,
|
|
344
|
-
$@"{{""sourcePaths"":[""{src2}""],""destinationPaths"":[""{TmpFolder}/move_b_done.txt""]}}");
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
[UnityTest]
|
|
348
|
-
public IEnumerator AssetsDelete_BothThreads()
|
|
349
|
-
{
|
|
350
|
-
var src1 = CreateTmpTextAsset("del_a.txt", "a");
|
|
351
|
-
yield return RunToolMainThreadCoop(Tool_Assets.AssetsDeleteToolId, $@"{{""paths"":[""{src1}""]}}");
|
|
352
|
-
|
|
353
|
-
var src2 = CreateTmpTextAsset("del_b.txt", "b");
|
|
354
|
-
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsDeleteToolId,
|
|
355
|
-
$@"{{""paths"":[""{src2}""]}}");
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
// ---------- Prefabs ----------
|
|
359
|
-
|
|
360
|
-
[UnityTest]
|
|
361
|
-
public IEnumerator AssetsPrefabCreate_BothThreads()
|
|
362
|
-
{
|
|
363
|
-
EnsureTmpFolder();
|
|
364
|
-
var idA = new GameObject("bg_prefab_a").GetEntityId();
|
|
365
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
366
|
-
$@"{{""prefabAssetPath"":""{TmpFolder}/prefab_a.prefab"",""gameObjectRef"":{{""instanceID"":{idA}}}}}");
|
|
367
|
-
|
|
368
|
-
var idB = new GameObject("bg_prefab_b").GetEntityId();
|
|
369
|
-
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
370
|
-
$@"{{""prefabAssetPath"":""{TmpFolder}/prefab_b.prefab"",""gameObjectRef"":{{""instanceID"":{idB}}}}}");
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
[UnityTest]
|
|
374
|
-
public IEnumerator AssetsPrefabInstantiate_BothThreads()
|
|
375
|
-
{
|
|
376
|
-
EnsureTmpFolder();
|
|
377
|
-
var seedId = new GameObject("bg_seed_prefab").GetEntityId();
|
|
378
|
-
var prefabPath = $"{TmpFolder}/seed.prefab";
|
|
379
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
380
|
-
$@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectRef"":{{""instanceID"":{seedId}}}}}");
|
|
381
|
-
|
|
382
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
383
|
-
$@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_main""}}");
|
|
384
|
-
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
385
|
-
$@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_bg""}}");
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
[UnityTest]
|
|
389
|
-
public IEnumerator AssetsPrefabOpenSaveClose_BothThreads()
|
|
390
|
-
{
|
|
391
|
-
EnsureTmpFolder();
|
|
392
|
-
|
|
393
|
-
// Create a prefab asset from a scene GameObject.
|
|
394
|
-
var seedA = new GameObject("bg_prefab_osc_a").GetEntityId();
|
|
395
|
-
var prefabPathA = $"{TmpFolder}/osc_a.prefab";
|
|
396
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
397
|
-
$@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectRef"":{{""instanceID"":{seedA}}}}}");
|
|
398
|
-
|
|
399
|
-
// Instantiate it so we get a prefab instance to open.
|
|
400
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
401
|
-
$@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectPath"":""bg_osc_inst_a""}}");
|
|
402
|
-
var instanceA = GameObject.Find("bg_osc_inst_a");
|
|
403
|
-
Assert.IsNotNull(instanceA, "Prefab instance not found after instantiate.");
|
|
404
|
-
|
|
405
|
-
// Open → Save → Close (main thread).
|
|
406
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
|
|
407
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{instanceA!.GetEntityId()}}}}}");
|
|
408
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
|
|
409
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
|
|
410
|
-
|
|
411
|
-
// Same sequence from a background thread.
|
|
412
|
-
var seedB = new GameObject("bg_prefab_osc_b").GetEntityId();
|
|
413
|
-
var prefabPathB = $"{TmpFolder}/osc_b.prefab";
|
|
414
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
415
|
-
$@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectRef"":{{""instanceID"":{seedB}}}}}");
|
|
416
|
-
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
417
|
-
$@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectPath"":""bg_osc_inst_b""}}");
|
|
418
|
-
var instanceB = GameObject.Find("bg_osc_inst_b");
|
|
419
|
-
Assert.IsNotNull(instanceB, "Prefab instance not found after instantiate.");
|
|
420
|
-
|
|
421
|
-
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
|
|
422
|
-
$@"{{""gameObjectRef"":{{""instanceID"":{instanceB!.GetEntityId()}}}}}");
|
|
423
|
-
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
|
|
424
|
-
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
|
|
425
|
-
}
|
|
426
|
-
|
|
427
|
-
// ================================================================
|
|
428
|
-
// SCENE tools
|
|
429
|
-
// ================================================================
|
|
430
|
-
|
|
431
|
-
[UnityTest]
|
|
432
|
-
public IEnumerator SceneCreate_BothThreads()
|
|
433
|
-
{
|
|
434
|
-
EnsureTmpFolder();
|
|
435
|
-
var startingActive = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
|
436
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
437
|
-
$@"{{""path"":""{TmpFolder}/scene_main.unity""}}");
|
|
438
|
-
yield return RunToolFromBackgroundThread(Tool_Scene.SceneCreateToolId,
|
|
439
|
-
$@"{{""path"":""{TmpFolder}/scene_bg.unity""}}");
|
|
440
|
-
|
|
441
|
-
// Restore a baseline active scene if the test clobbered it.
|
|
442
|
-
if (startingActive.IsValid() && UnityEngine.SceneManagement.SceneManager.GetActiveScene() != startingActive)
|
|
443
|
-
UnityEngine.SceneManagement.SceneManager.SetActiveScene(startingActive);
|
|
444
|
-
}
|
|
445
|
-
|
|
446
|
-
[UnityTest]
|
|
447
|
-
public IEnumerator SceneOpenSaveSetActive_BothThreads()
|
|
448
|
-
{
|
|
449
|
-
EnsureTmpFolder();
|
|
450
|
-
var scenePath = $"{TmpFolder}/reopen.unity";
|
|
451
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId, $@"{{""path"":""{scenePath}""}}");
|
|
452
|
-
|
|
453
|
-
// open (main + bg)
|
|
454
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneOpenToolId,
|
|
455
|
-
$@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
|
|
456
|
-
yield return RunToolFromBackgroundThread(Tool_Scene.SceneOpenToolId,
|
|
457
|
-
$@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
|
|
458
|
-
|
|
459
|
-
// set-active (both)
|
|
460
|
-
yield return RunToolBothThreads(Tool_Scene.SceneSetActiveToolId,
|
|
461
|
-
$@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
|
|
462
|
-
|
|
463
|
-
// save (both)
|
|
464
|
-
yield return RunToolBothThreads(Tool_Scene.SceneSaveToolId, "{}");
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
[UnityTest]
|
|
468
|
-
public IEnumerator SceneUnload_BothThreads()
|
|
469
|
-
{
|
|
470
|
-
EnsureTmpFolder();
|
|
471
|
-
|
|
472
|
-
// Create a saved scene so we can open additional scenes additively.
|
|
473
|
-
var basePath = $"{TmpFolder}/unload_base.unity";
|
|
474
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
475
|
-
$@"{{""path"":""{basePath}""}}");
|
|
476
|
-
|
|
477
|
-
// Create two extra scenes additively (allowed now because base is saved).
|
|
478
|
-
var mainPath = $"{TmpFolder}/unload_main.unity";
|
|
479
|
-
var bgPath = $"{TmpFolder}/unload_bg.unity";
|
|
480
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
481
|
-
$@"{{""path"":""{mainPath}"",""newSceneMode"":""Additive""}}");
|
|
482
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
483
|
-
$@"{{""path"":""{bgPath}"",""newSceneMode"":""Additive""}}");
|
|
484
|
-
|
|
485
|
-
// Unload each additive scene from both threads (tool takes scene name, not path).
|
|
486
|
-
yield return RunToolMainThreadCoop(Tool_Scene.SceneUnloadToolId,
|
|
487
|
-
@"{""name"":""unload_main""}");
|
|
488
|
-
yield return RunToolFromBackgroundThread(Tool_Scene.SceneUnloadToolId,
|
|
489
|
-
@"{""name"":""unload_bg""}");
|
|
490
|
-
}
|
|
491
|
-
|
|
492
|
-
// ================================================================
|
|
493
|
-
// SCRIPT tools
|
|
494
|
-
// ================================================================
|
|
495
|
-
|
|
496
|
-
[UnityTest]
|
|
497
|
-
public IEnumerator ScriptUpdateOrCreate_ReadDelete_BothThreads()
|
|
498
|
-
{
|
|
499
|
-
EnsureTmpFolder();
|
|
500
|
-
var pathA = $"{TmpFolder}/S_a.cs";
|
|
501
|
-
var pathB = $"{TmpFolder}/S_b.cs";
|
|
502
|
-
const string SrcA = "public class BgSa { public static void M() {} }";
|
|
503
|
-
const string SrcB = "public class BgSb { public static void M() {} }";
|
|
504
|
-
|
|
505
|
-
// update-or-create
|
|
506
|
-
yield return RunToolMainThreadCoop(Tool_Script.ScriptUpdateOrCreateToolId,
|
|
507
|
-
$@"{{""filePath"":""{pathA}"",""content"":{JsonString(SrcA)}}}");
|
|
508
|
-
yield return RunToolFromBackgroundThread(Tool_Script.ScriptUpdateOrCreateToolId,
|
|
509
|
-
$@"{{""filePath"":""{pathB}"",""content"":{JsonString(SrcB)}}}");
|
|
510
|
-
|
|
511
|
-
// read (main + bg)
|
|
512
|
-
yield return RunToolBothThreads(Tool_Script.ScriptReadToolId,
|
|
513
|
-
$@"{{""filePath"":""{pathA}""}}",
|
|
514
|
-
$@"{{""filePath"":""{pathB}""}}");
|
|
515
|
-
|
|
516
|
-
// delete
|
|
517
|
-
yield return RunToolMainThreadCoop(Tool_Script.ScriptDeleteToolId, $@"{{""files"":[""{pathA}""]}}");
|
|
518
|
-
yield return RunToolFromBackgroundThread(Tool_Script.ScriptDeleteToolId,
|
|
519
|
-
$@"{{""files"":[""{pathB}""]}}");
|
|
520
|
-
}
|
|
521
|
-
|
|
522
|
-
[UnityTest]
|
|
523
|
-
public IEnumerator ScriptExecute_BothThreads()
|
|
524
|
-
{
|
|
525
|
-
const string Code = "public class Script { public static int Main() { return 42; } }";
|
|
526
|
-
yield return RunToolBothThreads(Tool_Script.ScriptExecuteToolId,
|
|
527
|
-
$@"{{""csharpCode"":{JsonString(Code)},""className"":""Script"",""methodName"":""Main""}}");
|
|
528
|
-
}
|
|
529
|
-
|
|
530
|
-
// ================================================================
|
|
531
|
-
// TOOL CONTROL
|
|
532
|
-
// ================================================================
|
|
533
|
-
|
|
534
|
-
[UnityTest]
|
|
535
|
-
public IEnumerator ToolSetEnabledState_BothThreads()
|
|
536
|
-
{
|
|
537
|
-
// Enable unity-tool-list twice (idempotent, already enabled) from both threads.
|
|
538
|
-
yield return RunToolBothThreads(Tool_Tool.ToolSetEnabledStateId,
|
|
539
|
-
@"{""tools"":[{""name"":""unity-tool-list"",""enabled"":true}]}");
|
|
540
|
-
}
|
|
541
|
-
|
|
542
|
-
// ================================================================
|
|
543
|
-
// REFLECTION (complex MethodRef schema → thread-safety only)
|
|
544
|
-
// ================================================================
|
|
545
|
-
|
|
546
|
-
[UnityTest]
|
|
547
|
-
public IEnumerator ReflectionMethodFind_ThreadSafetyOnly()
|
|
548
|
-
{
|
|
549
|
-
yield return RunToolExpectNoThreadViolation(Tool_Reflection.ReflectionMethodFindToolId,
|
|
550
|
-
@"{""filter"":{""Namespace"":""UnityEngine"",""TypeName"":""Transform"",""MethodName"":""LookAt""}}");
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
[UnityTest]
|
|
554
|
-
public IEnumerator ReflectionMethodCall_BothThreads()
|
|
555
|
-
{
|
|
556
|
-
// Call a static method on a test-scoped GameObject's Transform via instance method.
|
|
557
|
-
var go = new GameObject("bg_reflect");
|
|
558
|
-
var id = go.GetEntityId();
|
|
559
|
-
yield return RunToolBothThreads(Tool_Reflection.ReflectionMethodCallToolId,
|
|
560
|
-
$@"{{""filter"":{{""Namespace"":""UnityEngine"",""TypeName"":""GameObject"",""MethodName"":""Find"",""InputParameters"":[{{""TypeName"":""System.String"",""Name"":""name""}}]}},""inputParameters"":[{{""typeName"":""System.String"",""name"":""name"",""value"":""bg_reflect""}}]}}");
|
|
561
|
-
}
|
|
562
|
-
|
|
563
|
-
// ================================================================
|
|
564
|
-
// PACKAGE MANAGER — package-add / package-remove intentionally NOT
|
|
565
|
-
// exercised here: both return status "Processing" and kick off a real
|
|
566
|
-
// Unity PackageManager resolution on a background task, which in turn
|
|
567
|
-
// forces a domain reload. Under the test runner that hangs the whole
|
|
568
|
-
// batch-mode run. Their thread-safety is already covered by the fact
|
|
569
|
-
// that their entry path is a synchronous parameter check.
|
|
570
|
-
// ================================================================
|
|
571
|
-
|
|
572
|
-
[UnityTest]
|
|
573
|
-
public IEnumerator PackageSearch_ThreadSafetyOnly()
|
|
574
|
-
{
|
|
575
|
-
// Registry round-trip is slow and flaky under test; thread-safety only.
|
|
576
|
-
yield return RunToolExpectNoThreadViolation(Tool_Package.PackageSearchToolId,
|
|
577
|
-
@"{""query"":""com.unity.textmeshpro"",""maxResults"":1,""offlineMode"":true}");
|
|
578
|
-
}
|
|
579
|
-
|
|
580
|
-
// ================================================================
|
|
581
|
-
// SCREENSHOTS — not guaranteed to have a camera/view in batchmode.
|
|
582
|
-
// ================================================================
|
|
583
|
-
|
|
584
|
-
[UnityTest]
|
|
585
|
-
public IEnumerator ScreenshotCamera_BothThreads()
|
|
586
|
-
{
|
|
587
|
-
new GameObject("TestCamera").AddComponent<Camera>();
|
|
588
|
-
yield return RunToolBothThreads(Tool_Screenshot.ScreenshotCameraToolId, @"{""width"":64,""height"":64}");
|
|
589
|
-
}
|
|
590
|
-
|
|
591
|
-
[UnityTest]
|
|
592
|
-
public IEnumerator ScreenshotGameView_ThreadSafetyOnly()
|
|
593
|
-
=> RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotGameViewToolId, "{}");
|
|
594
|
-
|
|
595
|
-
[UnityTest]
|
|
596
|
-
public IEnumerator ScreenshotSceneView_ThreadSafetyOnly()
|
|
597
|
-
=> RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotSceneViewToolId, @"{""width"":64,""height"":64}");
|
|
598
|
-
|
|
599
|
-
// ================================================================
|
|
600
|
-
// TESTS — recursive; skip deliberately.
|
|
601
|
-
// ================================================================
|
|
602
|
-
// tests-run intentionally has no test here: executing it from within the test
|
|
603
|
-
// runner causes recursion and a hang. Its thread safety is implicit because the
|
|
604
|
-
// test runner itself schedules the invocation onto the main thread.
|
|
605
|
-
|
|
606
|
-
// ================================================================
|
|
607
|
-
// Helpers
|
|
608
|
-
// ================================================================
|
|
609
|
-
|
|
610
|
-
static void EnsureTmpFolder()
|
|
611
|
-
{
|
|
612
|
-
if (!AssetDatabase.IsValidFolder(TmpFolder))
|
|
613
|
-
AssetDatabase.CreateFolder("Assets", TmpFolderName);
|
|
614
|
-
}
|
|
615
|
-
|
|
616
|
-
static string CreateTmpTextAsset(string fileName, string contents)
|
|
617
|
-
{
|
|
618
|
-
EnsureTmpFolder();
|
|
619
|
-
var path = $"{TmpFolder}/{fileName}";
|
|
620
|
-
File.WriteAllText(path, contents);
|
|
621
|
-
AssetDatabase.ImportAsset(path);
|
|
622
|
-
return path;
|
|
623
|
-
}
|
|
624
|
-
|
|
625
|
-
static string? FindFirstShaderPath()
|
|
626
|
-
{
|
|
627
|
-
// Prefer the built-in Standard shader.
|
|
628
|
-
var standard = Shader.Find("Standard");
|
|
629
|
-
if (standard != null)
|
|
630
|
-
{
|
|
631
|
-
var p = AssetDatabase.GetAssetPath(standard);
|
|
632
|
-
if (!string.IsNullOrEmpty(p))
|
|
633
|
-
return p;
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
var guids = AssetDatabase.FindAssets("t:Shader");
|
|
637
|
-
foreach (var guid in guids)
|
|
638
|
-
{
|
|
639
|
-
var p = AssetDatabase.GUIDToAssetPath(guid);
|
|
640
|
-
if (!string.IsNullOrEmpty(p))
|
|
641
|
-
return p;
|
|
642
|
-
}
|
|
643
|
-
return null;
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
/// <summary>Returns a JSON string literal (quoted, backslash-escaped).</summary>
|
|
647
|
-
static string JsonString(string raw)
|
|
648
|
-
=> System.Text.Json.JsonSerializer.Serialize(raw);
|
|
649
|
-
}
|
|
650
|
-
}
|
|
651
|
-
#endif
|
|
1
|
+
/*
|
|
2
|
+
┌──────────────────────────────────────────────────────────────────┐
|
|
3
|
+
│ Author: Ivan Murzak (https://github.com/IvanMurzak) │
|
|
4
|
+
│ Repository: GitHub (https://github.com/IvanMurzak/Unity-MCP) │
|
|
5
|
+
│ Copyright (c) 2025 Ivan Murzak │
|
|
6
|
+
│ Licensed under the Apache License, Version 2.0. │
|
|
7
|
+
│ See the LICENSE file in the project root for more information. │
|
|
8
|
+
└──────────────────────────────────────────────────────────────────┘
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
#nullable enable
|
|
12
|
+
#if UNITY_6000_5_OR_NEWER
|
|
13
|
+
using System.Collections;
|
|
14
|
+
using System.IO;
|
|
15
|
+
using com.AtelierAI.Unity.Copilot.Editor.API;
|
|
16
|
+
using NUnit.Framework;
|
|
17
|
+
using UnityEditor;
|
|
18
|
+
using UnityEngine;
|
|
19
|
+
using UnityEngine.TestTools;
|
|
20
|
+
|
|
21
|
+
namespace com.AtelierAI.Unity.Copilot.Editor.Tests
|
|
22
|
+
{
|
|
23
|
+
/// <summary>
|
|
24
|
+
/// Thread-safety coverage for Uco tools — regression for
|
|
25
|
+
/// <see href="https://github.com/IvanMurzak/uco-plugin/issues/633">#633</see>.
|
|
26
|
+
///
|
|
27
|
+
/// For each tool we want to guarantee the tool's body handles invocation from any
|
|
28
|
+
/// thread (the Uco server dispatches tool calls from a SignalR background thread,
|
|
29
|
+
/// so tools that touch Unity APIs must marshal back to the main thread via
|
|
30
|
+
/// <c>MainThread.Instance.Run</c>). Forgetting to do so caused the regression in
|
|
31
|
+
/// <see href="https://github.com/IvanMurzak/uco-plugin/issues/632">#632</see> /
|
|
32
|
+
/// <see href="https://github.com/IvanMurzak/uco-plugin/pull/637">#637</see>
|
|
33
|
+
/// (<c>console-clear-logs</c>).
|
|
34
|
+
///
|
|
35
|
+
/// <b>Excluded tools</b>: <c>package-add</c>/<c>package-remove</c> (trigger domain
|
|
36
|
+
/// reload), <c>tests-run</c> (recursive hang), system tools (<c>ping</c>,
|
|
37
|
+
/// <c>unity-skill-*</c>). See inline comments for rationale.
|
|
38
|
+
///
|
|
39
|
+
/// Two helpers from <see cref="BaseTest"/> are used:
|
|
40
|
+
/// <list type="bullet">
|
|
41
|
+
/// <item><see cref="BaseTest.RunToolBothThreads"/> — happy path; asserts the tool
|
|
42
|
+
/// succeeds on both threads. Used when crafting a valid input is practical.</item>
|
|
43
|
+
/// <item><see cref="BaseTest.RunToolExpectNoThreadViolation"/> — lenient mode; tolerates
|
|
44
|
+
/// business-level errors and only fails the test if Unity surfaces a
|
|
45
|
+
/// <c>"can only be called from the main thread"</c> exception. Used for tools
|
|
46
|
+
/// that need elaborate setup (<c>SerializedMember</c>, prefab stages, etc.) or
|
|
47
|
+
/// whose happy path would mutate the project in risky ways
|
|
48
|
+
/// (<c>package-add</c>/<c>package-remove</c>, <c>tests-run</c>, etc.).</item>
|
|
49
|
+
/// </list>
|
|
50
|
+
/// </summary>
|
|
51
|
+
public class ToolThreadSafetyTests : BaseTest
|
|
52
|
+
{
|
|
53
|
+
const string TmpFolderName = "BgThreadTests_TMP";
|
|
54
|
+
const string TmpFolder = "Assets/" + TmpFolderName;
|
|
55
|
+
|
|
56
|
+
[TearDown]
|
|
57
|
+
public void BgTearDown()
|
|
58
|
+
{
|
|
59
|
+
if (AssetDatabase.IsValidFolder(TmpFolder))
|
|
60
|
+
AssetDatabase.DeleteAsset(TmpFolder);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// ================================================================
|
|
64
|
+
// NO-ARG / READ-ONLY tools (happy path, empty JSON)
|
|
65
|
+
// ================================================================
|
|
66
|
+
|
|
67
|
+
[UnityTest] public IEnumerator ToolList_BothThreads()
|
|
68
|
+
=> RunToolBothThreads(Tool_Tool.ToolListId, "{}");
|
|
69
|
+
|
|
70
|
+
[UnityTest] public IEnumerator GameObjectComponentListAll_BothThreads()
|
|
71
|
+
=> RunToolBothThreads(Tool_GameObject.ComponentListToolId, @"{""pageSize"":3}");
|
|
72
|
+
|
|
73
|
+
[UnityTest] public IEnumerator SceneListOpened_BothThreads()
|
|
74
|
+
=> RunToolBothThreads(Tool_Scene.SceneListOpenedToolId, "{}");
|
|
75
|
+
|
|
76
|
+
[UnityTest] public IEnumerator SceneGetData_BothThreads()
|
|
77
|
+
=> RunToolBothThreads(Tool_Scene.SceneGetDataToolId, "{}");
|
|
78
|
+
|
|
79
|
+
[UnityTest] public IEnumerator EditorApplicationGetState_BothThreads()
|
|
80
|
+
=> RunToolBothThreads(Tool_Editor.EditorApplicationGetStateToolId, "{}");
|
|
81
|
+
|
|
82
|
+
[UnityTest] public IEnumerator EditorSelectionGet_BothThreads()
|
|
83
|
+
=> RunToolBothThreads(Tool_Editor_Selection.EditorSelectionGetToolId, "{}");
|
|
84
|
+
|
|
85
|
+
[UnityTest] public IEnumerator ConsoleGetLogs_BothThreads()
|
|
86
|
+
=> RunToolBothThreads(Tool_Console.ConsoleGetLogsToolId, "{}");
|
|
87
|
+
|
|
88
|
+
[UnityTest]
|
|
89
|
+
public IEnumerator ConsoleClearLogs_BothThreads()
|
|
90
|
+
{
|
|
91
|
+
// Regression for #632/#637: ClearLogs calls Debug.ClearDeveloperConsole() which
|
|
92
|
+
// MUST be invoked on the main thread.
|
|
93
|
+
yield return RunToolBothThreads(Tool_Console.ConsoleClearLogsToolId, "{}");
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
[UnityTest] public IEnumerator AssetsFind_BothThreads()
|
|
97
|
+
=> RunToolBothThreads(Tool_Assets.AssetsFindToolId, @"{""filter"":""t:Scene"",""maxResults"":3}");
|
|
98
|
+
|
|
99
|
+
[UnityTest] public IEnumerator AssetsFindBuiltIn_BothThreads()
|
|
100
|
+
=> RunToolBothThreads(Tool_Assets.AssetsFindBuiltInToolId, @"{""maxResults"":3}");
|
|
101
|
+
|
|
102
|
+
[UnityTest] public IEnumerator AssetsShaderListAll_BothThreads()
|
|
103
|
+
=> RunToolBothThreads(Tool_Assets_Shader.AssetsShaderListAllToolId, "{}");
|
|
104
|
+
|
|
105
|
+
[UnityTest] public IEnumerator AssetsRefresh_BothThreads()
|
|
106
|
+
=> RunToolBothThreads(Tool_Assets.AssetsRefreshToolId, "{}");
|
|
107
|
+
|
|
108
|
+
[UnityTest] public IEnumerator PackageList_BothThreads()
|
|
109
|
+
=> RunToolBothThreads(Tool_Package.PackageListToolId, "{}");
|
|
110
|
+
|
|
111
|
+
[UnityTest] public IEnumerator TypeGetJsonSchema_BothThreads()
|
|
112
|
+
=> RunToolBothThreads(Tool_Type.TypeGetJsonSchemaToolId, @"{""typeName"":""UnityEngine.Vector3""}");
|
|
113
|
+
|
|
114
|
+
// ================================================================
|
|
115
|
+
// GAMEOBJECT tools — happy path, per-call setup
|
|
116
|
+
// ================================================================
|
|
117
|
+
|
|
118
|
+
[UnityTest]
|
|
119
|
+
public IEnumerator GameObjectCreate_BothThreads()
|
|
120
|
+
{
|
|
121
|
+
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_main""}");
|
|
122
|
+
Assert.IsNotNull(GameObject.Find("bg_main"));
|
|
123
|
+
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectCreateToolId, @"{""name"":""bg_bg""}");
|
|
124
|
+
Assert.IsNotNull(GameObject.Find("bg_bg"));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[UnityTest]
|
|
128
|
+
public IEnumerator GameObjectFind_BothThreads()
|
|
129
|
+
{
|
|
130
|
+
var go = new GameObject("bg_find");
|
|
131
|
+
var id = go.GetEntityId();
|
|
132
|
+
yield return RunToolBothThreads(Tool_GameObject.GameObjectFindToolId,
|
|
133
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{id}}}}}");
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
[UnityTest]
|
|
137
|
+
public IEnumerator GameObjectDestroy_BothThreads()
|
|
138
|
+
{
|
|
139
|
+
var a = new GameObject("bg_destroy_a").GetEntityId();
|
|
140
|
+
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDestroyToolId,
|
|
141
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{a}}}}}");
|
|
142
|
+
|
|
143
|
+
var b = new GameObject("bg_destroy_b").GetEntityId();
|
|
144
|
+
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDestroyToolId,
|
|
145
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{b}}}}}");
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
[UnityTest]
|
|
149
|
+
public IEnumerator GameObjectDuplicate_BothThreads()
|
|
150
|
+
{
|
|
151
|
+
var a = new GameObject("bg_dup_a").GetEntityId();
|
|
152
|
+
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectDuplicateToolId,
|
|
153
|
+
$@"{{""gameObjectRefs"":[{{""instanceID"":{a}}}]}}");
|
|
154
|
+
|
|
155
|
+
var b = new GameObject("bg_dup_b").GetEntityId();
|
|
156
|
+
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectDuplicateToolId,
|
|
157
|
+
$@"{{""gameObjectRefs"":[{{""instanceID"":{b}}}]}}");
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
[UnityTest]
|
|
161
|
+
public IEnumerator GameObjectSetParent_BothThreads()
|
|
162
|
+
{
|
|
163
|
+
var parent = new GameObject("bg_parent").GetEntityId();
|
|
164
|
+
var child1 = new GameObject("bg_child_a").GetEntityId();
|
|
165
|
+
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectSetParentToolId,
|
|
166
|
+
$@"{{""gameObjectRefs"":[{{""instanceID"":{child1}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
|
|
167
|
+
|
|
168
|
+
var child2 = new GameObject("bg_child_b").GetEntityId();
|
|
169
|
+
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectSetParentToolId,
|
|
170
|
+
$@"{{""gameObjectRefs"":[{{""instanceID"":{child2}}}],""parentGameObjectRef"":{{""instanceID"":{parent}}}}}");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
[UnityTest]
|
|
174
|
+
public IEnumerator GameObjectComponentAdd_BothThreads()
|
|
175
|
+
{
|
|
176
|
+
var a = new GameObject("bg_addc_a").GetEntityId();
|
|
177
|
+
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentAddToolId,
|
|
178
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{a}}},""componentNames"":[""UnityEngine.BoxCollider""]}}");
|
|
179
|
+
|
|
180
|
+
var b = new GameObject("bg_addc_b").GetEntityId();
|
|
181
|
+
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentAddToolId,
|
|
182
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{b}}},""componentNames"":[""UnityEngine.SphereCollider""]}}");
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
[UnityTest]
|
|
186
|
+
public IEnumerator GameObjectComponentGet_BothThreads()
|
|
187
|
+
{
|
|
188
|
+
var go = new GameObject("bg_getc");
|
|
189
|
+
var id = go.GetEntityId();
|
|
190
|
+
// Transform lives at component index 0 on every GameObject.
|
|
191
|
+
yield return RunToolBothThreads(Tool_GameObject.GameObjectComponentGetToolId,
|
|
192
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""index"":0}}}}");
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
[UnityTest]
|
|
196
|
+
public IEnumerator GameObjectComponentDestroy_BothThreads()
|
|
197
|
+
{
|
|
198
|
+
var a = new GameObject("bg_destc_a");
|
|
199
|
+
a.AddComponent<BoxCollider>();
|
|
200
|
+
var idA = a.GetEntityId();
|
|
201
|
+
yield return RunToolMainThreadCoop(Tool_GameObject.GameObjectComponentDestroyToolId,
|
|
202
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{idA}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.BoxCollider""}}]}}");
|
|
203
|
+
|
|
204
|
+
var b = new GameObject("bg_destc_b");
|
|
205
|
+
b.AddComponent<SphereCollider>();
|
|
206
|
+
var idB = b.GetEntityId();
|
|
207
|
+
yield return RunToolFromBackgroundThread(Tool_GameObject.GameObjectComponentDestroyToolId,
|
|
208
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{idB}}},""destroyComponentRefs"":[{{""typeName"":""UnityEngine.SphereCollider""}}]}}");
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
[UnityTest]
|
|
212
|
+
public IEnumerator GameObjectModify_ThreadSafetyOnly()
|
|
213
|
+
{
|
|
214
|
+
// gameobject-modify requires a SerializedMember tree — we cover thread safety
|
|
215
|
+
// only (business errors from an empty diff are tolerated).
|
|
216
|
+
var id = new GameObject("bg_modify").GetEntityId();
|
|
217
|
+
yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectModifyToolId,
|
|
218
|
+
$@"{{""gameObjectRefs"":[{{""instanceID"":{id}}}],""gameObjectDiffs"":[{{""name"":""root""}}]}}");
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
[UnityTest]
|
|
222
|
+
public IEnumerator GameObjectComponentModify_ThreadSafetyOnly()
|
|
223
|
+
{
|
|
224
|
+
var go = new GameObject("bg_cmodify");
|
|
225
|
+
go.AddComponent<BoxCollider>();
|
|
226
|
+
var id = go.GetEntityId();
|
|
227
|
+
yield return RunToolExpectNoThreadViolation(Tool_GameObject.GameObjectComponentModifyToolId,
|
|
228
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{id}}},""componentRef"":{{""typeName"":""UnityEngine.BoxCollider""}},""componentDiff"":{{""name"":""root""}}}}");
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// ================================================================
|
|
232
|
+
// OBJECT tools
|
|
233
|
+
// ================================================================
|
|
234
|
+
|
|
235
|
+
[UnityTest]
|
|
236
|
+
public IEnumerator ObjectGetData_BothThreads()
|
|
237
|
+
{
|
|
238
|
+
var id = new GameObject("bg_obj").GetEntityId();
|
|
239
|
+
yield return RunToolBothThreads(Tool_Object.ObjectGetDataToolId,
|
|
240
|
+
$@"{{""objectRef"":{{""instanceID"":{id}}}}}");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
[UnityTest]
|
|
244
|
+
public IEnumerator ObjectModify_ThreadSafetyOnly()
|
|
245
|
+
{
|
|
246
|
+
var id = new GameObject("bg_objmod").GetEntityId();
|
|
247
|
+
yield return RunToolExpectNoThreadViolation(Tool_Object.ObjectModifyToolId,
|
|
248
|
+
$@"{{""objectRef"":{{""instanceID"":{id}}},""objectDiff"":{{""name"":""root""}}}}");
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
// ================================================================
|
|
252
|
+
// EDITOR tools
|
|
253
|
+
// ================================================================
|
|
254
|
+
|
|
255
|
+
[UnityTest]
|
|
256
|
+
public IEnumerator EditorSelectionSet_BothThreads()
|
|
257
|
+
{
|
|
258
|
+
var id = new GameObject("bg_select").GetEntityId();
|
|
259
|
+
yield return RunToolBothThreads(Tool_Editor_Selection.EditorSelectionSetToolId,
|
|
260
|
+
$@"{{""select"":[{{""instanceID"":{id}}}]}}");
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
[UnityTest]
|
|
264
|
+
public IEnumerator EditorApplicationSetState_BothThreads()
|
|
265
|
+
{
|
|
266
|
+
// No-op toggle: current state is not-playing, not-paused.
|
|
267
|
+
yield return RunToolBothThreads(Tool_Editor.EditorApplicationSetStateToolId,
|
|
268
|
+
@"{""isPlaying"":false,""isPaused"":false}");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
// ================================================================
|
|
272
|
+
// ASSETS tools
|
|
273
|
+
// ================================================================
|
|
274
|
+
|
|
275
|
+
[UnityTest]
|
|
276
|
+
public IEnumerator AssetsCreateFolder_BothThreads()
|
|
277
|
+
{
|
|
278
|
+
EnsureTmpFolder();
|
|
279
|
+
yield return RunToolMainThreadCoop(Tool_Assets.AssetsCreateFolderToolId,
|
|
280
|
+
$@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""a""}}]}}");
|
|
281
|
+
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCreateFolderToolId,
|
|
282
|
+
$@"{{""inputs"":[{{""parentFolderPath"":""{TmpFolder}"",""newFolderName"":""b""}}]}}");
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
[UnityTest]
|
|
286
|
+
public IEnumerator AssetsGetData_BothThreads()
|
|
287
|
+
{
|
|
288
|
+
var assetPath = CreateTmpTextAsset("get_data.txt", "hello");
|
|
289
|
+
yield return RunToolBothThreads(Tool_Assets.AssetsGetDataToolId,
|
|
290
|
+
$@"{{""assetRef"":{{""assetPath"":""{assetPath}""}}}}");
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
[UnityTest]
|
|
294
|
+
public IEnumerator AssetsModify_ThreadSafetyOnly()
|
|
295
|
+
{
|
|
296
|
+
var assetPath = CreateTmpTextAsset("modify.txt", "hello");
|
|
297
|
+
yield return RunToolExpectNoThreadViolation(Tool_Assets.AssetsModifyToolId,
|
|
298
|
+
$@"{{""assetRef"":{{""assetPath"":""{assetPath}""}},""content"":{{""name"":""root""}}}}");
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
[UnityTest]
|
|
302
|
+
public IEnumerator AssetsMaterialCreate_BothThreads()
|
|
303
|
+
{
|
|
304
|
+
EnsureTmpFolder();
|
|
305
|
+
yield return RunToolMainThreadCoop(Tool_Assets.AssetsMaterialCreateToolId,
|
|
306
|
+
$@"{{""assetPath"":""{TmpFolder}/mat_main.mat"",""shaderName"":""Standard""}}");
|
|
307
|
+
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMaterialCreateToolId,
|
|
308
|
+
$@"{{""assetPath"":""{TmpFolder}/mat_bg.mat"",""shaderName"":""Standard""}}");
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
[UnityTest]
|
|
312
|
+
public IEnumerator AssetsShaderGetData_BothThreads()
|
|
313
|
+
{
|
|
314
|
+
// Find any shader asset in the project to query.
|
|
315
|
+
var shaderPath = FindFirstShaderPath();
|
|
316
|
+
if (shaderPath == null)
|
|
317
|
+
{
|
|
318
|
+
Assert.Ignore("No shader asset available in project.");
|
|
319
|
+
yield break;
|
|
320
|
+
}
|
|
321
|
+
yield return RunToolBothThreads(Tool_Assets_Shader.AssetsShaderGetDataToolId,
|
|
322
|
+
$@"{{""assetRef"":{{""assetPath"":""{shaderPath}""}}}}");
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
[UnityTest]
|
|
326
|
+
public IEnumerator AssetsCopy_BothThreads()
|
|
327
|
+
{
|
|
328
|
+
var src = CreateTmpTextAsset("copy_src.txt", "source");
|
|
329
|
+
yield return RunToolMainThreadCoop(Tool_Assets.AssetsCopyToolId,
|
|
330
|
+
$@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_main.txt""]}}");
|
|
331
|
+
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsCopyToolId,
|
|
332
|
+
$@"{{""sourcePaths"":[""{src}""],""destinationPaths"":[""{TmpFolder}/copy_bg.txt""]}}");
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
[UnityTest]
|
|
336
|
+
public IEnumerator AssetsMove_BothThreads()
|
|
337
|
+
{
|
|
338
|
+
var src1 = CreateTmpTextAsset("move_a.txt", "a");
|
|
339
|
+
yield return RunToolMainThreadCoop(Tool_Assets.AssetsMoveToolId,
|
|
340
|
+
$@"{{""sourcePaths"":[""{src1}""],""destinationPaths"":[""{TmpFolder}/move_a_done.txt""]}}");
|
|
341
|
+
|
|
342
|
+
var src2 = CreateTmpTextAsset("move_b.txt", "b");
|
|
343
|
+
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsMoveToolId,
|
|
344
|
+
$@"{{""sourcePaths"":[""{src2}""],""destinationPaths"":[""{TmpFolder}/move_b_done.txt""]}}");
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
[UnityTest]
|
|
348
|
+
public IEnumerator AssetsDelete_BothThreads()
|
|
349
|
+
{
|
|
350
|
+
var src1 = CreateTmpTextAsset("del_a.txt", "a");
|
|
351
|
+
yield return RunToolMainThreadCoop(Tool_Assets.AssetsDeleteToolId, $@"{{""paths"":[""{src1}""]}}");
|
|
352
|
+
|
|
353
|
+
var src2 = CreateTmpTextAsset("del_b.txt", "b");
|
|
354
|
+
yield return RunToolFromBackgroundThread(Tool_Assets.AssetsDeleteToolId,
|
|
355
|
+
$@"{{""paths"":[""{src2}""]}}");
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
// ---------- Prefabs ----------
|
|
359
|
+
|
|
360
|
+
[UnityTest]
|
|
361
|
+
public IEnumerator AssetsPrefabCreate_BothThreads()
|
|
362
|
+
{
|
|
363
|
+
EnsureTmpFolder();
|
|
364
|
+
var idA = new GameObject("bg_prefab_a").GetEntityId();
|
|
365
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
366
|
+
$@"{{""prefabAssetPath"":""{TmpFolder}/prefab_a.prefab"",""gameObjectRef"":{{""instanceID"":{idA}}}}}");
|
|
367
|
+
|
|
368
|
+
var idB = new GameObject("bg_prefab_b").GetEntityId();
|
|
369
|
+
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
370
|
+
$@"{{""prefabAssetPath"":""{TmpFolder}/prefab_b.prefab"",""gameObjectRef"":{{""instanceID"":{idB}}}}}");
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
[UnityTest]
|
|
374
|
+
public IEnumerator AssetsPrefabInstantiate_BothThreads()
|
|
375
|
+
{
|
|
376
|
+
EnsureTmpFolder();
|
|
377
|
+
var seedId = new GameObject("bg_seed_prefab").GetEntityId();
|
|
378
|
+
var prefabPath = $"{TmpFolder}/seed.prefab";
|
|
379
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
380
|
+
$@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectRef"":{{""instanceID"":{seedId}}}}}");
|
|
381
|
+
|
|
382
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
383
|
+
$@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_main""}}");
|
|
384
|
+
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
385
|
+
$@"{{""prefabAssetPath"":""{prefabPath}"",""gameObjectPath"":""bg_inst_bg""}}");
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
[UnityTest]
|
|
389
|
+
public IEnumerator AssetsPrefabOpenSaveClose_BothThreads()
|
|
390
|
+
{
|
|
391
|
+
EnsureTmpFolder();
|
|
392
|
+
|
|
393
|
+
// Create a prefab asset from a scene GameObject.
|
|
394
|
+
var seedA = new GameObject("bg_prefab_osc_a").GetEntityId();
|
|
395
|
+
var prefabPathA = $"{TmpFolder}/osc_a.prefab";
|
|
396
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
397
|
+
$@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectRef"":{{""instanceID"":{seedA}}}}}");
|
|
398
|
+
|
|
399
|
+
// Instantiate it so we get a prefab instance to open.
|
|
400
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
401
|
+
$@"{{""prefabAssetPath"":""{prefabPathA}"",""gameObjectPath"":""bg_osc_inst_a""}}");
|
|
402
|
+
var instanceA = GameObject.Find("bg_osc_inst_a");
|
|
403
|
+
Assert.IsNotNull(instanceA, "Prefab instance not found after instantiate.");
|
|
404
|
+
|
|
405
|
+
// Open → Save → Close (main thread).
|
|
406
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
|
|
407
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{instanceA!.GetEntityId()}}}}}");
|
|
408
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
|
|
409
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
|
|
410
|
+
|
|
411
|
+
// Same sequence from a background thread.
|
|
412
|
+
var seedB = new GameObject("bg_prefab_osc_b").GetEntityId();
|
|
413
|
+
var prefabPathB = $"{TmpFolder}/osc_b.prefab";
|
|
414
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabCreateToolId,
|
|
415
|
+
$@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectRef"":{{""instanceID"":{seedB}}}}}");
|
|
416
|
+
yield return RunToolMainThreadCoop(Tool_Assets_Prefab.AssetsPrefabInstantiateToolId,
|
|
417
|
+
$@"{{""prefabAssetPath"":""{prefabPathB}"",""gameObjectPath"":""bg_osc_inst_b""}}");
|
|
418
|
+
var instanceB = GameObject.Find("bg_osc_inst_b");
|
|
419
|
+
Assert.IsNotNull(instanceB, "Prefab instance not found after instantiate.");
|
|
420
|
+
|
|
421
|
+
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabOpenToolId,
|
|
422
|
+
$@"{{""gameObjectRef"":{{""instanceID"":{instanceB!.GetEntityId()}}}}}");
|
|
423
|
+
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabSaveToolId, "{}");
|
|
424
|
+
yield return RunToolFromBackgroundThread(Tool_Assets_Prefab.AssetsPrefabCloseToolId, @"{""save"":false}");
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// ================================================================
|
|
428
|
+
// SCENE tools
|
|
429
|
+
// ================================================================
|
|
430
|
+
|
|
431
|
+
[UnityTest]
|
|
432
|
+
public IEnumerator SceneCreate_BothThreads()
|
|
433
|
+
{
|
|
434
|
+
EnsureTmpFolder();
|
|
435
|
+
var startingActive = UnityEngine.SceneManagement.SceneManager.GetActiveScene();
|
|
436
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
437
|
+
$@"{{""path"":""{TmpFolder}/scene_main.unity""}}");
|
|
438
|
+
yield return RunToolFromBackgroundThread(Tool_Scene.SceneCreateToolId,
|
|
439
|
+
$@"{{""path"":""{TmpFolder}/scene_bg.unity""}}");
|
|
440
|
+
|
|
441
|
+
// Restore a baseline active scene if the test clobbered it.
|
|
442
|
+
if (startingActive.IsValid() && UnityEngine.SceneManagement.SceneManager.GetActiveScene() != startingActive)
|
|
443
|
+
UnityEngine.SceneManagement.SceneManager.SetActiveScene(startingActive);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
[UnityTest]
|
|
447
|
+
public IEnumerator SceneOpenSaveSetActive_BothThreads()
|
|
448
|
+
{
|
|
449
|
+
EnsureTmpFolder();
|
|
450
|
+
var scenePath = $"{TmpFolder}/reopen.unity";
|
|
451
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId, $@"{{""path"":""{scenePath}""}}");
|
|
452
|
+
|
|
453
|
+
// open (main + bg)
|
|
454
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneOpenToolId,
|
|
455
|
+
$@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
|
|
456
|
+
yield return RunToolFromBackgroundThread(Tool_Scene.SceneOpenToolId,
|
|
457
|
+
$@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
|
|
458
|
+
|
|
459
|
+
// set-active (both)
|
|
460
|
+
yield return RunToolBothThreads(Tool_Scene.SceneSetActiveToolId,
|
|
461
|
+
$@"{{""sceneRef"":{{""assetPath"":""{scenePath}""}}}}");
|
|
462
|
+
|
|
463
|
+
// save (both)
|
|
464
|
+
yield return RunToolBothThreads(Tool_Scene.SceneSaveToolId, "{}");
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
[UnityTest]
|
|
468
|
+
public IEnumerator SceneUnload_BothThreads()
|
|
469
|
+
{
|
|
470
|
+
EnsureTmpFolder();
|
|
471
|
+
|
|
472
|
+
// Create a saved scene so we can open additional scenes additively.
|
|
473
|
+
var basePath = $"{TmpFolder}/unload_base.unity";
|
|
474
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
475
|
+
$@"{{""path"":""{basePath}""}}");
|
|
476
|
+
|
|
477
|
+
// Create two extra scenes additively (allowed now because base is saved).
|
|
478
|
+
var mainPath = $"{TmpFolder}/unload_main.unity";
|
|
479
|
+
var bgPath = $"{TmpFolder}/unload_bg.unity";
|
|
480
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
481
|
+
$@"{{""path"":""{mainPath}"",""newSceneMode"":""Additive""}}");
|
|
482
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneCreateToolId,
|
|
483
|
+
$@"{{""path"":""{bgPath}"",""newSceneMode"":""Additive""}}");
|
|
484
|
+
|
|
485
|
+
// Unload each additive scene from both threads (tool takes scene name, not path).
|
|
486
|
+
yield return RunToolMainThreadCoop(Tool_Scene.SceneUnloadToolId,
|
|
487
|
+
@"{""name"":""unload_main""}");
|
|
488
|
+
yield return RunToolFromBackgroundThread(Tool_Scene.SceneUnloadToolId,
|
|
489
|
+
@"{""name"":""unload_bg""}");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
// ================================================================
|
|
493
|
+
// SCRIPT tools
|
|
494
|
+
// ================================================================
|
|
495
|
+
|
|
496
|
+
[UnityTest]
|
|
497
|
+
public IEnumerator ScriptUpdateOrCreate_ReadDelete_BothThreads()
|
|
498
|
+
{
|
|
499
|
+
EnsureTmpFolder();
|
|
500
|
+
var pathA = $"{TmpFolder}/S_a.cs";
|
|
501
|
+
var pathB = $"{TmpFolder}/S_b.cs";
|
|
502
|
+
const string SrcA = "public class BgSa { public static void M() {} }";
|
|
503
|
+
const string SrcB = "public class BgSb { public static void M() {} }";
|
|
504
|
+
|
|
505
|
+
// update-or-create
|
|
506
|
+
yield return RunToolMainThreadCoop(Tool_Script.ScriptUpdateOrCreateToolId,
|
|
507
|
+
$@"{{""filePath"":""{pathA}"",""content"":{JsonString(SrcA)}}}");
|
|
508
|
+
yield return RunToolFromBackgroundThread(Tool_Script.ScriptUpdateOrCreateToolId,
|
|
509
|
+
$@"{{""filePath"":""{pathB}"",""content"":{JsonString(SrcB)}}}");
|
|
510
|
+
|
|
511
|
+
// read (main + bg)
|
|
512
|
+
yield return RunToolBothThreads(Tool_Script.ScriptReadToolId,
|
|
513
|
+
$@"{{""filePath"":""{pathA}""}}",
|
|
514
|
+
$@"{{""filePath"":""{pathB}""}}");
|
|
515
|
+
|
|
516
|
+
// delete
|
|
517
|
+
yield return RunToolMainThreadCoop(Tool_Script.ScriptDeleteToolId, $@"{{""files"":[""{pathA}""]}}");
|
|
518
|
+
yield return RunToolFromBackgroundThread(Tool_Script.ScriptDeleteToolId,
|
|
519
|
+
$@"{{""files"":[""{pathB}""]}}");
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
[UnityTest]
|
|
523
|
+
public IEnumerator ScriptExecute_BothThreads()
|
|
524
|
+
{
|
|
525
|
+
const string Code = "public class Script { public static int Main() { return 42; } }";
|
|
526
|
+
yield return RunToolBothThreads(Tool_Script.ScriptExecuteToolId,
|
|
527
|
+
$@"{{""csharpCode"":{JsonString(Code)},""className"":""Script"",""methodName"":""Main""}}");
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
// ================================================================
|
|
531
|
+
// TOOL CONTROL
|
|
532
|
+
// ================================================================
|
|
533
|
+
|
|
534
|
+
[UnityTest]
|
|
535
|
+
public IEnumerator ToolSetEnabledState_BothThreads()
|
|
536
|
+
{
|
|
537
|
+
// Enable unity-tool-list twice (idempotent, already enabled) from both threads.
|
|
538
|
+
yield return RunToolBothThreads(Tool_Tool.ToolSetEnabledStateId,
|
|
539
|
+
@"{""tools"":[{""name"":""unity-tool-list"",""enabled"":true}]}");
|
|
540
|
+
}
|
|
541
|
+
|
|
542
|
+
// ================================================================
|
|
543
|
+
// REFLECTION (complex MethodRef schema → thread-safety only)
|
|
544
|
+
// ================================================================
|
|
545
|
+
|
|
546
|
+
[UnityTest]
|
|
547
|
+
public IEnumerator ReflectionMethodFind_ThreadSafetyOnly()
|
|
548
|
+
{
|
|
549
|
+
yield return RunToolExpectNoThreadViolation(Tool_Reflection.ReflectionMethodFindToolId,
|
|
550
|
+
@"{""filter"":{""Namespace"":""UnityEngine"",""TypeName"":""Transform"",""MethodName"":""LookAt""}}");
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
[UnityTest]
|
|
554
|
+
public IEnumerator ReflectionMethodCall_BothThreads()
|
|
555
|
+
{
|
|
556
|
+
// Call a static method on a test-scoped GameObject's Transform via instance method.
|
|
557
|
+
var go = new GameObject("bg_reflect");
|
|
558
|
+
var id = go.GetEntityId();
|
|
559
|
+
yield return RunToolBothThreads(Tool_Reflection.ReflectionMethodCallToolId,
|
|
560
|
+
$@"{{""filter"":{{""Namespace"":""UnityEngine"",""TypeName"":""GameObject"",""MethodName"":""Find"",""InputParameters"":[{{""TypeName"":""System.String"",""Name"":""name""}}]}},""inputParameters"":[{{""typeName"":""System.String"",""name"":""name"",""value"":""bg_reflect""}}]}}");
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
// ================================================================
|
|
564
|
+
// PACKAGE MANAGER — package-add / package-remove intentionally NOT
|
|
565
|
+
// exercised here: both return status "Processing" and kick off a real
|
|
566
|
+
// Unity PackageManager resolution on a background task, which in turn
|
|
567
|
+
// forces a domain reload. Under the test runner that hangs the whole
|
|
568
|
+
// batch-mode run. Their thread-safety is already covered by the fact
|
|
569
|
+
// that their entry path is a synchronous parameter check.
|
|
570
|
+
// ================================================================
|
|
571
|
+
|
|
572
|
+
[UnityTest]
|
|
573
|
+
public IEnumerator PackageSearch_ThreadSafetyOnly()
|
|
574
|
+
{
|
|
575
|
+
// Registry round-trip is slow and flaky under test; thread-safety only.
|
|
576
|
+
yield return RunToolExpectNoThreadViolation(Tool_Package.PackageSearchToolId,
|
|
577
|
+
@"{""query"":""com.unity.textmeshpro"",""maxResults"":1,""offlineMode"":true}");
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// ================================================================
|
|
581
|
+
// SCREENSHOTS — not guaranteed to have a camera/view in batchmode.
|
|
582
|
+
// ================================================================
|
|
583
|
+
|
|
584
|
+
[UnityTest]
|
|
585
|
+
public IEnumerator ScreenshotCamera_BothThreads()
|
|
586
|
+
{
|
|
587
|
+
new GameObject("TestCamera").AddComponent<Camera>();
|
|
588
|
+
yield return RunToolBothThreads(Tool_Screenshot.ScreenshotCameraToolId, @"{""width"":64,""height"":64}");
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
[UnityTest]
|
|
592
|
+
public IEnumerator ScreenshotGameView_ThreadSafetyOnly()
|
|
593
|
+
=> RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotGameViewToolId, "{}");
|
|
594
|
+
|
|
595
|
+
[UnityTest]
|
|
596
|
+
public IEnumerator ScreenshotSceneView_ThreadSafetyOnly()
|
|
597
|
+
=> RunToolExpectNoThreadViolation(Tool_Screenshot.ScreenshotSceneViewToolId, @"{""width"":64,""height"":64}");
|
|
598
|
+
|
|
599
|
+
// ================================================================
|
|
600
|
+
// TESTS — recursive; skip deliberately.
|
|
601
|
+
// ================================================================
|
|
602
|
+
// tests-run intentionally has no test here: executing it from within the test
|
|
603
|
+
// runner causes recursion and a hang. Its thread safety is implicit because the
|
|
604
|
+
// test runner itself schedules the invocation onto the main thread.
|
|
605
|
+
|
|
606
|
+
// ================================================================
|
|
607
|
+
// Helpers
|
|
608
|
+
// ================================================================
|
|
609
|
+
|
|
610
|
+
static void EnsureTmpFolder()
|
|
611
|
+
{
|
|
612
|
+
if (!AssetDatabase.IsValidFolder(TmpFolder))
|
|
613
|
+
AssetDatabase.CreateFolder("Assets", TmpFolderName);
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
static string CreateTmpTextAsset(string fileName, string contents)
|
|
617
|
+
{
|
|
618
|
+
EnsureTmpFolder();
|
|
619
|
+
var path = $"{TmpFolder}/{fileName}";
|
|
620
|
+
File.WriteAllText(path, contents);
|
|
621
|
+
AssetDatabase.ImportAsset(path);
|
|
622
|
+
return path;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
static string? FindFirstShaderPath()
|
|
626
|
+
{
|
|
627
|
+
// Prefer the built-in Standard shader.
|
|
628
|
+
var standard = Shader.Find("Standard");
|
|
629
|
+
if (standard != null)
|
|
630
|
+
{
|
|
631
|
+
var p = AssetDatabase.GetAssetPath(standard);
|
|
632
|
+
if (!string.IsNullOrEmpty(p))
|
|
633
|
+
return p;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
var guids = AssetDatabase.FindAssets("t:Shader");
|
|
637
|
+
foreach (var guid in guids)
|
|
638
|
+
{
|
|
639
|
+
var p = AssetDatabase.GUIDToAssetPath(guid);
|
|
640
|
+
if (!string.IsNullOrEmpty(p))
|
|
641
|
+
return p;
|
|
642
|
+
}
|
|
643
|
+
return null;
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
/// <summary>Returns a JSON string literal (quoted, backslash-escaped).</summary>
|
|
647
|
+
static string JsonString(string raw)
|
|
648
|
+
=> System.Text.Json.JsonSerializer.Serialize(raw);
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
#endif
|