@atelierai/uco 1.0.7 → 1.0.9
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 +29 -8
- package/package.json +2 -2
- package/vendor/plugin/com.atelierai.unity.copilot/CHANGELOG.md +4 -4
- 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/API/Tool/ToolGroups.SetEnabled.cs +92 -92
- 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 +887 -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/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 +22 -22
- 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/ToolGroupRegistry.cs +461 -460
- 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/Editor/UI/uss/CopilotPromptsWindow.uss +11 -11
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotResourcesWindow.uss +16 -16
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/CopilotToolsWindow.uss +36 -36
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/MainWindow.uss +183 -183
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/{_mcp-list-window.uss → _copilot-list-window.uss} +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/_list-view.uss +77 -77
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotPromptsWindow.uxml +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotResourcesWindow.uxml +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/CopilotToolsWindow.uxml +18 -18
- package/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uxml/MainWindow.uxml +115 -115
- 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/README.md +88 -88
- 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 +3 -3
- 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 +11 -11
- 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/NuGetExtractorDevelopmentDependencyTests.cs +148 -148
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetExtractorFlatLayoutTests.cs +166 -166
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetInstallManifestTests.cs +436 -436
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetLegacyMigrationTests.cs +304 -304
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/DependencyResolver/NuGetPackageInstallerStaleVersionCleanupTests.cs +200 -200
- package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/Environment/EnvironmentUtilsTests.cs +2 -2
- 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/GameObject/TestJsonSchema.cs +199 -199
- 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/vendor/plugin/com.atelierai.unity.copilot/Editor/UI/uss/common/{_mcp-list-window.uss.meta → _copilot-list-window.uss.meta} +0 -0
- /package/vendor/plugin/com.atelierai.unity.copilot/Tests/Editor/{McpPluginTests.cs.meta → UcoPluginTests.cs.meta} +0 -0
|
@@ -1,304 +1,304 @@
|
|
|
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
|
-
using System.IO;
|
|
13
|
-
using System.Runtime.InteropServices;
|
|
14
|
-
using System.Text.RegularExpressions;
|
|
15
|
-
using NUnit.Framework;
|
|
16
|
-
using UnityEngine.TestTools;
|
|
17
|
-
using com.AtelierAI.Unity.Copilot.Editor.DependencyResolver;
|
|
18
|
-
|
|
19
|
-
namespace com.AtelierAI.Unity.Copilot.Editor.Tests.DependencyResolverTests
|
|
20
|
-
{
|
|
21
|
-
/// <summary>
|
|
22
|
-
/// Coverage for issue #733's mandatory legacy → flat-layout migration. Every
|
|
23
|
-
/// existing user upgrading to the new resolver still has DLLs sitting under
|
|
24
|
-
/// <c>Assets/Plugins/NuGet/{Id}.{Version}/</c> from a pre-fix install. The
|
|
25
|
-
/// migration MUST delete those folders before any flat-layout DLL is
|
|
26
|
-
/// written, in the same restore cycle, otherwise the project ends up with
|
|
27
|
-
/// duplicate copies of every assembly and Unity's compiler errors with
|
|
28
|
-
/// CS0436 / CS0433.
|
|
29
|
-
/// </summary>
|
|
30
|
-
[TestFixture]
|
|
31
|
-
public class NuGetLegacyMigrationTests
|
|
32
|
-
{
|
|
33
|
-
string _installPath = string.Empty;
|
|
34
|
-
|
|
35
|
-
[SetUp]
|
|
36
|
-
public void SetUp()
|
|
37
|
-
{
|
|
38
|
-
_installPath = Path.Combine(
|
|
39
|
-
Path.GetTempPath(),
|
|
40
|
-
"
|
|
41
|
-
Directory.CreateDirectory(_installPath);
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
[TearDown]
|
|
45
|
-
public void TearDown()
|
|
46
|
-
{
|
|
47
|
-
if (Directory.Exists(_installPath))
|
|
48
|
-
{
|
|
49
|
-
try { Directory.Delete(_installPath, recursive: true); }
|
|
50
|
-
catch { /* best-effort cleanup */ }
|
|
51
|
-
}
|
|
52
|
-
}
|
|
53
|
-
|
|
54
|
-
[Test]
|
|
55
|
-
public void Run_NoLegacyState_ReturnsNoLegacyState_OnEmptyInstallPath()
|
|
56
|
-
{
|
|
57
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
58
|
-
|
|
59
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.NoLegacyState, result.Outcome);
|
|
60
|
-
Assert.AreEqual(0, result.RemovedItems.Count);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
[Test]
|
|
64
|
-
public void Run_NoLegacyState_ReturnsNoLegacyState_OnUnversionedFlatInstall()
|
|
65
|
-
{
|
|
66
|
-
// Steady-state install in the unversioned flat layout: no legacy
|
|
67
|
-
// {Id}.{Version}/ directories, no versioned-filename {stem}.{v}.dll
|
|
68
|
-
// siblings — the migration must short-circuit and touch nothing.
|
|
69
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Memory.dll"), "dummy");
|
|
70
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Memory.dll.meta"), "meta");
|
|
71
|
-
File.WriteAllText(Path.Combine(_installPath, ".nuget-installed.json"), "{}");
|
|
72
|
-
|
|
73
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
74
|
-
|
|
75
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.NoLegacyState, result.Outcome);
|
|
76
|
-
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "System.Memory.dll")));
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
[Test]
|
|
80
|
-
public void Run_HappyPath_RemovesLegacyDirectoriesAndMetas()
|
|
81
|
-
{
|
|
82
|
-
// Seed several legacy directories: single-DLL package and a multi-DLL package.
|
|
83
|
-
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
84
|
-
CreateLegacyPackage("Microsoft.Bcl.Memory", "10.0.3",
|
|
85
|
-
"System.Memory.dll", "System.Buffers.dll", "System.Runtime.CompilerServices.Unsafe.dll");
|
|
86
|
-
|
|
87
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
88
|
-
|
|
89
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
90
|
-
Assert.AreEqual(2, result.RemovedItems.Count);
|
|
91
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
92
|
-
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5.meta")));
|
|
93
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3")));
|
|
94
|
-
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3.meta")));
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
[Test]
|
|
98
|
-
public void Run_Idempotent_SecondRunIsNoOp()
|
|
99
|
-
{
|
|
100
|
-
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
101
|
-
|
|
102
|
-
var first = NuGetLegacyMigration.Run(_installPath);
|
|
103
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, first.Outcome);
|
|
104
|
-
|
|
105
|
-
var second = NuGetLegacyMigration.Run(_installPath);
|
|
106
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.NoLegacyState, second.Outcome);
|
|
107
|
-
Assert.AreEqual(0, second.RemovedItems.Count);
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
[Test]
|
|
111
|
-
public void Run_MixedLegacyState_MigratesLegacyDirsAndVersionedFiles_LeavesUnrelatedContent()
|
|
112
|
-
{
|
|
113
|
-
// Mixed install: a legacy {Id}.{Version}/ folder, a flat
|
|
114
|
-
// versioned-filename DLL, and unrelated user content. The migration
|
|
115
|
-
// sweeps both legacy artifacts in one pass and leaves the user's
|
|
116
|
-
// directory + unversioned canonical untouched.
|
|
117
|
-
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
118
|
-
File.WriteAllText(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3.dll"), "flat-versioned-leftover");
|
|
119
|
-
File.WriteAllText(Path.Combine(_installPath, "Microsoft.Bcl.Memory.dll"), "canonical");
|
|
120
|
-
Directory.CreateDirectory(Path.Combine(_installPath, "ReadMe"));
|
|
121
|
-
File.WriteAllText(Path.Combine(_installPath, "ReadMe", "notes.txt"), "user notes");
|
|
122
|
-
|
|
123
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
124
|
-
|
|
125
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
126
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
127
|
-
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3.dll")),
|
|
128
|
-
"Versioned-filename flat DLLs are now legacy and must be swept.");
|
|
129
|
-
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.dll")),
|
|
130
|
-
"Unversioned canonical filenames must not be touched by migration.");
|
|
131
|
-
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "ReadMe")),
|
|
132
|
-
"Non-package directories must not be touched by migration.");
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
[Test]
|
|
136
|
-
public void Run_VersionedFilenameDllsAtRoot_AreSwept()
|
|
137
|
-
{
|
|
138
|
-
// Migration from the pre-unversioned-filename resolver: any flat
|
|
139
|
-
// {stem}.{numericVersion}.dll at the install root is by
|
|
140
|
-
// definition stale once the canonical filename is just {stem}.dll.
|
|
141
|
-
// The migration sweeps them in the same pass that removes
|
|
142
|
-
// legacy {Id}.{Version}/ directories.
|
|
143
|
-
File.WriteAllText(Path.Combine(_installPath, "
|
|
144
|
-
File.WriteAllText(Path.Combine(_installPath, "
|
|
145
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll"), "stale");
|
|
146
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll.meta"), "meta");
|
|
147
|
-
// Unversioned canonicals must NOT be touched.
|
|
148
|
-
File.WriteAllText(Path.Combine(_installPath, "ReadMe.dll"), "user-content");
|
|
149
|
-
File.WriteAllText(Path.Combine(_installPath, "Foo.dll"), "canonical");
|
|
150
|
-
|
|
151
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
152
|
-
|
|
153
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
154
|
-
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "
|
|
155
|
-
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "
|
|
156
|
-
".meta sidecar must be deleted alongside the DLL.");
|
|
157
|
-
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll")));
|
|
158
|
-
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "ReadMe.dll")),
|
|
159
|
-
"Unversioned filenames are the new canonical and must be preserved.");
|
|
160
|
-
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "Foo.dll")));
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
[Test]
|
|
164
|
-
public void Run_LegacyDirWithSemVerPrerelease_RemovedAsLegacy()
|
|
165
|
-
{
|
|
166
|
-
// NuGet folder names can contain SemVer prerelease / build-metadata
|
|
167
|
-
// suffixes (e.g. `Microsoft.Extensions.Logging.8.0.1-preview`).
|
|
168
|
-
// System.Version.TryParse rejects those tails, so an earlier version
|
|
169
|
-
// of the migration silently left those folders on disk and the user
|
|
170
|
-
// ended up with both the legacy nested DLL and the new flat-layout
|
|
171
|
-
// DLL coexisting — duplicate-assembly compile errors. Pin the
|
|
172
|
-
// SemVer-shape fallback in `ExtractPackageIdFromDirName` so the
|
|
173
|
-
// migration removes them.
|
|
174
|
-
CreateLegacyPackage("Foo.Bar", "1.0.0-preview", "Foo.Bar.dll");
|
|
175
|
-
CreateLegacyPackage("Baz", "2.3.4+build.42", "Baz.dll");
|
|
176
|
-
CreateLegacyPackage("Qux", "1.0.0-rc.1", "Qux.dll");
|
|
177
|
-
|
|
178
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
179
|
-
|
|
180
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
181
|
-
Assert.AreEqual(3, result.RemovedItems.Count);
|
|
182
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Foo.Bar.1.0.0-preview")));
|
|
183
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Baz.2.3.4+build.42")));
|
|
184
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Qux.1.0.0-rc.1")));
|
|
185
|
-
}
|
|
186
|
-
|
|
187
|
-
#if UNITY_EDITOR_WIN
|
|
188
|
-
// Windows-only: simulates a `FileShare.None` lock that Unity holds on a
|
|
189
|
-
// DLL it has loaded into the editor AppDomain. Linux/macOS use advisory
|
|
190
|
-
// locking — `FileShare.None` does not prevent `File.Delete` there — so
|
|
191
|
-
// gating on `UNITY_EDITOR_WIN` keeps the assertion honest. The
|
|
192
|
-
// post-best-effort migration no longer aborts the entire restore on a
|
|
193
|
-
// single locked file: the call returns `AbortedFileLock` to surface
|
|
194
|
-
// that some directory is still on disk, but the caller may continue
|
|
195
|
-
// safely (the locked file's PluginImporter is disabled so the next
|
|
196
|
-
// domain reload unloads it and the next migration pass cleans it up).
|
|
197
|
-
[Test]
|
|
198
|
-
public void Run_FileLock_LeavesBlockedFolderIntactAndReportsAbortedFileLock()
|
|
199
|
-
{
|
|
200
|
-
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
201
|
-
var lockedDll = Path.Combine(_installPath, "System.Text.Json.8.0.5", "System.Text.Json.dll");
|
|
202
|
-
|
|
203
|
-
// Best-effort migration logs a warning (not an error) for blocked folders.
|
|
204
|
-
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(@"\[NuGet\] Could not fully remove legacy install directory"));
|
|
205
|
-
|
|
206
|
-
using (var lockHandle = new FileStream(lockedDll, FileMode.Open, FileAccess.Read, FileShare.None))
|
|
207
|
-
{
|
|
208
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
209
|
-
|
|
210
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.AbortedFileLock, result.Outcome);
|
|
211
|
-
Assert.IsNotNull(result.FirstFailedItem);
|
|
212
|
-
Assert.IsNotNull(result.FirstFailureMessage);
|
|
213
|
-
// Blocked folder still on disk — the next reload picks it back up.
|
|
214
|
-
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
[Test]
|
|
219
|
-
public void Run_FileLock_OneFolderBlocked_OtherFoldersAreStillRemoved()
|
|
220
|
-
{
|
|
221
|
-
// Mixed state: a blocked folder must NOT prevent unblocked folders
|
|
222
|
-
// from being cleaned up — that's the whole point of best-effort.
|
|
223
|
-
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
224
|
-
CreateLegacyPackage("R3", "1.3.0", "R3.dll");
|
|
225
|
-
var lockedDll = Path.Combine(_installPath, "System.Text.Json.8.0.5", "System.Text.Json.dll");
|
|
226
|
-
|
|
227
|
-
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(@"\[NuGet\] Could not fully remove legacy install directory"));
|
|
228
|
-
|
|
229
|
-
using (var lockHandle = new FileStream(lockedDll, FileMode.Open, FileAccess.Read, FileShare.None))
|
|
230
|
-
{
|
|
231
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
232
|
-
|
|
233
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.AbortedFileLock, result.Outcome);
|
|
234
|
-
Assert.AreEqual(1, result.RemovedItems.Count,
|
|
235
|
-
"The unblocked folder must still be removed even though a sibling folder was locked.");
|
|
236
|
-
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")),
|
|
237
|
-
"Blocked folder remains on disk for retry on the next pass.");
|
|
238
|
-
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "R3.1.3.0")),
|
|
239
|
-
"Unblocked folder must have been removed.");
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
#endif
|
|
243
|
-
|
|
244
|
-
#if !UNITY_EDITOR_WIN
|
|
245
|
-
// Unix counterpart of Run_FileLock_LeavesBlockedFolderIntactAndReportsAbortedFileLock.
|
|
246
|
-
// The migration catches both `IOException` (Windows file-lock) and
|
|
247
|
-
// `UnauthorizedAccessException` (Unix permission denial / antivirus /
|
|
248
|
-
// read-only flags) inside its per-file delete loop. Here we deny write
|
|
249
|
-
// on the install-path parent via `chmod 0o500`, which makes
|
|
250
|
-
// `File.Delete` and the trailing `Directory.Delete` both throw
|
|
251
|
-
// `UnauthorizedAccessException`. Self-skips when running as root.
|
|
252
|
-
[Test]
|
|
253
|
-
public void Run_PermissionDenied_LeavesBlockedFolderIntactAndReportsAbortedFileLock()
|
|
254
|
-
{
|
|
255
|
-
if (geteuid() == 0)
|
|
256
|
-
Assert.Ignore("chmod-based permission denial is bypassed by the kernel for uid=0; run this test as a non-root user.");
|
|
257
|
-
|
|
258
|
-
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
259
|
-
|
|
260
|
-
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(@"\[NuGet\] Could not fully remove legacy install directory"));
|
|
261
|
-
|
|
262
|
-
const uint READ_EXEC_ONLY = 0b101_000_000; // 0o500 — r-x------
|
|
263
|
-
const uint READ_WRITE_EXEC = 0b111_000_000; // 0o700 — rwx------ (TearDown-friendly)
|
|
264
|
-
if (chmod(_installPath, READ_EXEC_ONLY) != 0)
|
|
265
|
-
Assert.Inconclusive($"chmod {_installPath} -> 0o500 failed (errno={Marshal.GetLastWin32Error()})");
|
|
266
|
-
|
|
267
|
-
try
|
|
268
|
-
{
|
|
269
|
-
var result = NuGetLegacyMigration.Run(_installPath);
|
|
270
|
-
|
|
271
|
-
Assert.AreEqual(NuGetLegacyMigration.Outcome.AbortedFileLock, result.Outcome);
|
|
272
|
-
Assert.IsNotNull(result.FirstFailedItem);
|
|
273
|
-
Assert.IsNotNull(result.FirstFailureMessage);
|
|
274
|
-
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
275
|
-
}
|
|
276
|
-
finally
|
|
277
|
-
{
|
|
278
|
-
// Always restore so [TearDown]'s recursive delete of `_installPath` succeeds.
|
|
279
|
-
chmod(_installPath, READ_WRITE_EXEC);
|
|
280
|
-
}
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
[DllImport("libc", SetLastError = true)]
|
|
284
|
-
static extern int chmod(string pathname, uint mode);
|
|
285
|
-
|
|
286
|
-
[DllImport("libc", SetLastError = true)]
|
|
287
|
-
static extern uint geteuid();
|
|
288
|
-
#endif
|
|
289
|
-
|
|
290
|
-
/// <summary>
|
|
291
|
-
/// Creates a fake legacy <c>{Id}.{Version}/</c> directory with the
|
|
292
|
-
/// given DLL filenames inside, plus the sibling <c>.meta</c>.
|
|
293
|
-
/// </summary>
|
|
294
|
-
void CreateLegacyPackage(string id, string version, params string[] dllNames)
|
|
295
|
-
{
|
|
296
|
-
var dirName = $"{id}.{version}";
|
|
297
|
-
var dirPath = Path.Combine(_installPath, dirName);
|
|
298
|
-
Directory.CreateDirectory(dirPath);
|
|
299
|
-
foreach (var dll in dllNames)
|
|
300
|
-
File.WriteAllText(Path.Combine(dirPath, dll), "dummy");
|
|
301
|
-
File.WriteAllText(dirPath + ".meta", "fileFormatVersion: 2\nguid: 00000000000000000000000000000000\n");
|
|
302
|
-
}
|
|
303
|
-
}
|
|
304
|
-
}
|
|
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
|
+
using System.IO;
|
|
13
|
+
using System.Runtime.InteropServices;
|
|
14
|
+
using System.Text.RegularExpressions;
|
|
15
|
+
using NUnit.Framework;
|
|
16
|
+
using UnityEngine.TestTools;
|
|
17
|
+
using com.AtelierAI.Unity.Copilot.Editor.DependencyResolver;
|
|
18
|
+
|
|
19
|
+
namespace com.AtelierAI.Unity.Copilot.Editor.Tests.DependencyResolverTests
|
|
20
|
+
{
|
|
21
|
+
/// <summary>
|
|
22
|
+
/// Coverage for issue #733's mandatory legacy → flat-layout migration. Every
|
|
23
|
+
/// existing user upgrading to the new resolver still has DLLs sitting under
|
|
24
|
+
/// <c>Assets/Plugins/NuGet/{Id}.{Version}/</c> from a pre-fix install. The
|
|
25
|
+
/// migration MUST delete those folders before any flat-layout DLL is
|
|
26
|
+
/// written, in the same restore cycle, otherwise the project ends up with
|
|
27
|
+
/// duplicate copies of every assembly and Unity's compiler errors with
|
|
28
|
+
/// CS0436 / CS0433.
|
|
29
|
+
/// </summary>
|
|
30
|
+
[TestFixture]
|
|
31
|
+
public class NuGetLegacyMigrationTests
|
|
32
|
+
{
|
|
33
|
+
string _installPath = string.Empty;
|
|
34
|
+
|
|
35
|
+
[SetUp]
|
|
36
|
+
public void SetUp()
|
|
37
|
+
{
|
|
38
|
+
_installPath = Path.Combine(
|
|
39
|
+
Path.GetTempPath(),
|
|
40
|
+
"Uco-Migration-" + Path.GetRandomFileName());
|
|
41
|
+
Directory.CreateDirectory(_installPath);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
[TearDown]
|
|
45
|
+
public void TearDown()
|
|
46
|
+
{
|
|
47
|
+
if (Directory.Exists(_installPath))
|
|
48
|
+
{
|
|
49
|
+
try { Directory.Delete(_installPath, recursive: true); }
|
|
50
|
+
catch { /* best-effort cleanup */ }
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
[Test]
|
|
55
|
+
public void Run_NoLegacyState_ReturnsNoLegacyState_OnEmptyInstallPath()
|
|
56
|
+
{
|
|
57
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
58
|
+
|
|
59
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.NoLegacyState, result.Outcome);
|
|
60
|
+
Assert.AreEqual(0, result.RemovedItems.Count);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
[Test]
|
|
64
|
+
public void Run_NoLegacyState_ReturnsNoLegacyState_OnUnversionedFlatInstall()
|
|
65
|
+
{
|
|
66
|
+
// Steady-state install in the unversioned flat layout: no legacy
|
|
67
|
+
// {Id}.{Version}/ directories, no versioned-filename {stem}.{v}.dll
|
|
68
|
+
// siblings — the migration must short-circuit and touch nothing.
|
|
69
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Memory.dll"), "dummy");
|
|
70
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Memory.dll.meta"), "meta");
|
|
71
|
+
File.WriteAllText(Path.Combine(_installPath, ".nuget-installed.json"), "{}");
|
|
72
|
+
|
|
73
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
74
|
+
|
|
75
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.NoLegacyState, result.Outcome);
|
|
76
|
+
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "System.Memory.dll")));
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
[Test]
|
|
80
|
+
public void Run_HappyPath_RemovesLegacyDirectoriesAndMetas()
|
|
81
|
+
{
|
|
82
|
+
// Seed several legacy directories: single-DLL package and a multi-DLL package.
|
|
83
|
+
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
84
|
+
CreateLegacyPackage("Microsoft.Bcl.Memory", "10.0.3",
|
|
85
|
+
"System.Memory.dll", "System.Buffers.dll", "System.Runtime.CompilerServices.Unsafe.dll");
|
|
86
|
+
|
|
87
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
88
|
+
|
|
89
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
90
|
+
Assert.AreEqual(2, result.RemovedItems.Count);
|
|
91
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
92
|
+
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5.meta")));
|
|
93
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3")));
|
|
94
|
+
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3.meta")));
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
[Test]
|
|
98
|
+
public void Run_Idempotent_SecondRunIsNoOp()
|
|
99
|
+
{
|
|
100
|
+
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
101
|
+
|
|
102
|
+
var first = NuGetLegacyMigration.Run(_installPath);
|
|
103
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, first.Outcome);
|
|
104
|
+
|
|
105
|
+
var second = NuGetLegacyMigration.Run(_installPath);
|
|
106
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.NoLegacyState, second.Outcome);
|
|
107
|
+
Assert.AreEqual(0, second.RemovedItems.Count);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
[Test]
|
|
111
|
+
public void Run_MixedLegacyState_MigratesLegacyDirsAndVersionedFiles_LeavesUnrelatedContent()
|
|
112
|
+
{
|
|
113
|
+
// Mixed install: a legacy {Id}.{Version}/ folder, a flat
|
|
114
|
+
// versioned-filename DLL, and unrelated user content. The migration
|
|
115
|
+
// sweeps both legacy artifacts in one pass and leaves the user's
|
|
116
|
+
// directory + unversioned canonical untouched.
|
|
117
|
+
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
118
|
+
File.WriteAllText(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3.dll"), "flat-versioned-leftover");
|
|
119
|
+
File.WriteAllText(Path.Combine(_installPath, "Microsoft.Bcl.Memory.dll"), "canonical");
|
|
120
|
+
Directory.CreateDirectory(Path.Combine(_installPath, "ReadMe"));
|
|
121
|
+
File.WriteAllText(Path.Combine(_installPath, "ReadMe", "notes.txt"), "user notes");
|
|
122
|
+
|
|
123
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
124
|
+
|
|
125
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
126
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
127
|
+
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.10.0.3.dll")),
|
|
128
|
+
"Versioned-filename flat DLLs are now legacy and must be swept.");
|
|
129
|
+
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "Microsoft.Bcl.Memory.dll")),
|
|
130
|
+
"Unversioned canonical filenames must not be touched by migration.");
|
|
131
|
+
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "ReadMe")),
|
|
132
|
+
"Non-package directories must not be touched by migration.");
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
[Test]
|
|
136
|
+
public void Run_VersionedFilenameDllsAtRoot_AreSwept()
|
|
137
|
+
{
|
|
138
|
+
// Migration from the pre-unversioned-filename resolver: any flat
|
|
139
|
+
// {stem}.{numericVersion}.dll at the install root is by
|
|
140
|
+
// definition stale once the canonical filename is just {stem}.dll.
|
|
141
|
+
// The migration sweeps them in the same pass that removes
|
|
142
|
+
// legacy {Id}.{Version}/ directories.
|
|
143
|
+
File.WriteAllText(Path.Combine(_installPath, "UcoPlugin.6.2.1.dll"), "stale");
|
|
144
|
+
File.WriteAllText(Path.Combine(_installPath, "UcoPlugin.6.2.1.dll.meta"), "meta");
|
|
145
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll"), "stale");
|
|
146
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll.meta"), "meta");
|
|
147
|
+
// Unversioned canonicals must NOT be touched.
|
|
148
|
+
File.WriteAllText(Path.Combine(_installPath, "ReadMe.dll"), "user-content");
|
|
149
|
+
File.WriteAllText(Path.Combine(_installPath, "Foo.dll"), "canonical");
|
|
150
|
+
|
|
151
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
152
|
+
|
|
153
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
154
|
+
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "UcoPlugin.6.2.1.dll")));
|
|
155
|
+
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "UcoPlugin.6.2.1.dll.meta")),
|
|
156
|
+
".meta sidecar must be deleted alongside the DLL.");
|
|
157
|
+
Assert.IsFalse(File.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll")));
|
|
158
|
+
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "ReadMe.dll")),
|
|
159
|
+
"Unversioned filenames are the new canonical and must be preserved.");
|
|
160
|
+
Assert.IsTrue(File.Exists(Path.Combine(_installPath, "Foo.dll")));
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
[Test]
|
|
164
|
+
public void Run_LegacyDirWithSemVerPrerelease_RemovedAsLegacy()
|
|
165
|
+
{
|
|
166
|
+
// NuGet folder names can contain SemVer prerelease / build-metadata
|
|
167
|
+
// suffixes (e.g. `Microsoft.Extensions.Logging.8.0.1-preview`).
|
|
168
|
+
// System.Version.TryParse rejects those tails, so an earlier version
|
|
169
|
+
// of the migration silently left those folders on disk and the user
|
|
170
|
+
// ended up with both the legacy nested DLL and the new flat-layout
|
|
171
|
+
// DLL coexisting — duplicate-assembly compile errors. Pin the
|
|
172
|
+
// SemVer-shape fallback in `ExtractPackageIdFromDirName` so the
|
|
173
|
+
// migration removes them.
|
|
174
|
+
CreateLegacyPackage("Foo.Bar", "1.0.0-preview", "Foo.Bar.dll");
|
|
175
|
+
CreateLegacyPackage("Baz", "2.3.4+build.42", "Baz.dll");
|
|
176
|
+
CreateLegacyPackage("Qux", "1.0.0-rc.1", "Qux.dll");
|
|
177
|
+
|
|
178
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
179
|
+
|
|
180
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.Migrated, result.Outcome);
|
|
181
|
+
Assert.AreEqual(3, result.RemovedItems.Count);
|
|
182
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Foo.Bar.1.0.0-preview")));
|
|
183
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Baz.2.3.4+build.42")));
|
|
184
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "Qux.1.0.0-rc.1")));
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
#if UNITY_EDITOR_WIN
|
|
188
|
+
// Windows-only: simulates a `FileShare.None` lock that Unity holds on a
|
|
189
|
+
// DLL it has loaded into the editor AppDomain. Linux/macOS use advisory
|
|
190
|
+
// locking — `FileShare.None` does not prevent `File.Delete` there — so
|
|
191
|
+
// gating on `UNITY_EDITOR_WIN` keeps the assertion honest. The
|
|
192
|
+
// post-best-effort migration no longer aborts the entire restore on a
|
|
193
|
+
// single locked file: the call returns `AbortedFileLock` to surface
|
|
194
|
+
// that some directory is still on disk, but the caller may continue
|
|
195
|
+
// safely (the locked file's PluginImporter is disabled so the next
|
|
196
|
+
// domain reload unloads it and the next migration pass cleans it up).
|
|
197
|
+
[Test]
|
|
198
|
+
public void Run_FileLock_LeavesBlockedFolderIntactAndReportsAbortedFileLock()
|
|
199
|
+
{
|
|
200
|
+
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
201
|
+
var lockedDll = Path.Combine(_installPath, "System.Text.Json.8.0.5", "System.Text.Json.dll");
|
|
202
|
+
|
|
203
|
+
// Best-effort migration logs a warning (not an error) for blocked folders.
|
|
204
|
+
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(@"\[NuGet\] Could not fully remove legacy install directory"));
|
|
205
|
+
|
|
206
|
+
using (var lockHandle = new FileStream(lockedDll, FileMode.Open, FileAccess.Read, FileShare.None))
|
|
207
|
+
{
|
|
208
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
209
|
+
|
|
210
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.AbortedFileLock, result.Outcome);
|
|
211
|
+
Assert.IsNotNull(result.FirstFailedItem);
|
|
212
|
+
Assert.IsNotNull(result.FirstFailureMessage);
|
|
213
|
+
// Blocked folder still on disk — the next reload picks it back up.
|
|
214
|
+
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
[Test]
|
|
219
|
+
public void Run_FileLock_OneFolderBlocked_OtherFoldersAreStillRemoved()
|
|
220
|
+
{
|
|
221
|
+
// Mixed state: a blocked folder must NOT prevent unblocked folders
|
|
222
|
+
// from being cleaned up — that's the whole point of best-effort.
|
|
223
|
+
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
224
|
+
CreateLegacyPackage("R3", "1.3.0", "R3.dll");
|
|
225
|
+
var lockedDll = Path.Combine(_installPath, "System.Text.Json.8.0.5", "System.Text.Json.dll");
|
|
226
|
+
|
|
227
|
+
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(@"\[NuGet\] Could not fully remove legacy install directory"));
|
|
228
|
+
|
|
229
|
+
using (var lockHandle = new FileStream(lockedDll, FileMode.Open, FileAccess.Read, FileShare.None))
|
|
230
|
+
{
|
|
231
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
232
|
+
|
|
233
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.AbortedFileLock, result.Outcome);
|
|
234
|
+
Assert.AreEqual(1, result.RemovedItems.Count,
|
|
235
|
+
"The unblocked folder must still be removed even though a sibling folder was locked.");
|
|
236
|
+
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")),
|
|
237
|
+
"Blocked folder remains on disk for retry on the next pass.");
|
|
238
|
+
Assert.IsFalse(Directory.Exists(Path.Combine(_installPath, "R3.1.3.0")),
|
|
239
|
+
"Unblocked folder must have been removed.");
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
#endif
|
|
243
|
+
|
|
244
|
+
#if !UNITY_EDITOR_WIN
|
|
245
|
+
// Unix counterpart of Run_FileLock_LeavesBlockedFolderIntactAndReportsAbortedFileLock.
|
|
246
|
+
// The migration catches both `IOException` (Windows file-lock) and
|
|
247
|
+
// `UnauthorizedAccessException` (Unix permission denial / antivirus /
|
|
248
|
+
// read-only flags) inside its per-file delete loop. Here we deny write
|
|
249
|
+
// on the install-path parent via `chmod 0o500`, which makes
|
|
250
|
+
// `File.Delete` and the trailing `Directory.Delete` both throw
|
|
251
|
+
// `UnauthorizedAccessException`. Self-skips when running as root.
|
|
252
|
+
[Test]
|
|
253
|
+
public void Run_PermissionDenied_LeavesBlockedFolderIntactAndReportsAbortedFileLock()
|
|
254
|
+
{
|
|
255
|
+
if (geteuid() == 0)
|
|
256
|
+
Assert.Ignore("chmod-based permission denial is bypassed by the kernel for uid=0; run this test as a non-root user.");
|
|
257
|
+
|
|
258
|
+
CreateLegacyPackage("System.Text.Json", "8.0.5", "System.Text.Json.dll");
|
|
259
|
+
|
|
260
|
+
LogAssert.Expect(UnityEngine.LogType.Warning, new Regex(@"\[NuGet\] Could not fully remove legacy install directory"));
|
|
261
|
+
|
|
262
|
+
const uint READ_EXEC_ONLY = 0b101_000_000; // 0o500 — r-x------
|
|
263
|
+
const uint READ_WRITE_EXEC = 0b111_000_000; // 0o700 — rwx------ (TearDown-friendly)
|
|
264
|
+
if (chmod(_installPath, READ_EXEC_ONLY) != 0)
|
|
265
|
+
Assert.Inconclusive($"chmod {_installPath} -> 0o500 failed (errno={Marshal.GetLastWin32Error()})");
|
|
266
|
+
|
|
267
|
+
try
|
|
268
|
+
{
|
|
269
|
+
var result = NuGetLegacyMigration.Run(_installPath);
|
|
270
|
+
|
|
271
|
+
Assert.AreEqual(NuGetLegacyMigration.Outcome.AbortedFileLock, result.Outcome);
|
|
272
|
+
Assert.IsNotNull(result.FirstFailedItem);
|
|
273
|
+
Assert.IsNotNull(result.FirstFailureMessage);
|
|
274
|
+
Assert.IsTrue(Directory.Exists(Path.Combine(_installPath, "System.Text.Json.8.0.5")));
|
|
275
|
+
}
|
|
276
|
+
finally
|
|
277
|
+
{
|
|
278
|
+
// Always restore so [TearDown]'s recursive delete of `_installPath` succeeds.
|
|
279
|
+
chmod(_installPath, READ_WRITE_EXEC);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
[DllImport("libc", SetLastError = true)]
|
|
284
|
+
static extern int chmod(string pathname, uint mode);
|
|
285
|
+
|
|
286
|
+
[DllImport("libc", SetLastError = true)]
|
|
287
|
+
static extern uint geteuid();
|
|
288
|
+
#endif
|
|
289
|
+
|
|
290
|
+
/// <summary>
|
|
291
|
+
/// Creates a fake legacy <c>{Id}.{Version}/</c> directory with the
|
|
292
|
+
/// given DLL filenames inside, plus the sibling <c>.meta</c>.
|
|
293
|
+
/// </summary>
|
|
294
|
+
void CreateLegacyPackage(string id, string version, params string[] dllNames)
|
|
295
|
+
{
|
|
296
|
+
var dirName = $"{id}.{version}";
|
|
297
|
+
var dirPath = Path.Combine(_installPath, dirName);
|
|
298
|
+
Directory.CreateDirectory(dirPath);
|
|
299
|
+
foreach (var dll in dllNames)
|
|
300
|
+
File.WriteAllText(Path.Combine(dirPath, dll), "dummy");
|
|
301
|
+
File.WriteAllText(dirPath + ".meta", "fileFormatVersion: 2\nguid: 00000000000000000000000000000000\n");
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
}
|