@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,436 +1,436 @@
|
|
|
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.Linq;
|
|
14
|
-
using NUnit.Framework;
|
|
15
|
-
using com.AtelierAI.Unity.Copilot.Editor.DependencyResolver;
|
|
16
|
-
|
|
17
|
-
namespace com.AtelierAI.Unity.Copilot.Editor.Tests.DependencyResolverTests
|
|
18
|
-
{
|
|
19
|
-
/// <summary>
|
|
20
|
-
/// Coverage for the on-disk manifest <c>.nuget-installed.json</c> introduced
|
|
21
|
-
/// for the flat layout (issue #733). The manifest is the primary source of
|
|
22
|
-
/// truth for "which DLL belongs to which package at which version", with
|
|
23
|
-
/// versioned-filename parsing as the disaster-recovery fallback.
|
|
24
|
-
/// </summary>
|
|
25
|
-
[TestFixture]
|
|
26
|
-
public class NuGetInstallManifestTests
|
|
27
|
-
{
|
|
28
|
-
string _installPath = string.Empty;
|
|
29
|
-
|
|
30
|
-
[SetUp]
|
|
31
|
-
public void SetUp()
|
|
32
|
-
{
|
|
33
|
-
_installPath = Path.Combine(
|
|
34
|
-
Path.GetTempPath(),
|
|
35
|
-
"
|
|
36
|
-
Directory.CreateDirectory(_installPath);
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
[TearDown]
|
|
40
|
-
public void TearDown()
|
|
41
|
-
{
|
|
42
|
-
if (Directory.Exists(_installPath))
|
|
43
|
-
{
|
|
44
|
-
try { Directory.Delete(_installPath, recursive: true); }
|
|
45
|
-
catch { /* best-effort cleanup */ }
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
[Test]
|
|
50
|
-
public void Load_ReturnsEmptyManifest_WhenFileMissing()
|
|
51
|
-
{
|
|
52
|
-
var manifest = NuGetInstallManifest.Load(_installPath);
|
|
53
|
-
Assert.AreEqual(0, manifest.Packages.Count);
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
[Test]
|
|
57
|
-
public void SaveThenLoad_RoundTrips_AllFields()
|
|
58
|
-
{
|
|
59
|
-
var manifest = new InstallManifest();
|
|
60
|
-
var entry = new InstalledPackage("8.0.1");
|
|
61
|
-
entry.Dlls.Add("Microsoft.Extensions.Logging.8.0.1.dll");
|
|
62
|
-
entry.Dlls.Add("Microsoft.Extensions.Logging.Abstractions.8.0.3.dll");
|
|
63
|
-
manifest.Packages["Microsoft.Extensions.Logging"] = entry;
|
|
64
|
-
|
|
65
|
-
// Multi-DLL package with a different version.
|
|
66
|
-
var multi = new InstalledPackage("10.0.3");
|
|
67
|
-
multi.Dlls.Add("System.Memory.10.0.3.dll");
|
|
68
|
-
multi.Dlls.Add("System.Buffers.10.0.3.dll");
|
|
69
|
-
manifest.Packages["Microsoft.Bcl.Memory"] = multi;
|
|
70
|
-
|
|
71
|
-
// Empty-DLL entry (development-only dependency).
|
|
72
|
-
manifest.Packages["Microsoft.CodeAnalysis.Analyzers"] = new InstalledPackage("3.11.0");
|
|
73
|
-
|
|
74
|
-
NuGetInstallManifest.Save(_installPath, manifest);
|
|
75
|
-
|
|
76
|
-
var roundTrip = NuGetInstallManifest.Load(_installPath);
|
|
77
|
-
|
|
78
|
-
Assert.AreEqual(3, roundTrip.Packages.Count);
|
|
79
|
-
Assert.AreEqual("8.0.1", roundTrip.Packages["Microsoft.Extensions.Logging"].Version);
|
|
80
|
-
Assert.AreEqual(2, roundTrip.Packages["Microsoft.Extensions.Logging"].Dlls.Count);
|
|
81
|
-
Assert.AreEqual("10.0.3", roundTrip.Packages["Microsoft.Bcl.Memory"].Version);
|
|
82
|
-
CollectionAssert.AreEquivalent(
|
|
83
|
-
new[] { "System.Memory.10.0.3.dll", "System.Buffers.10.0.3.dll" },
|
|
84
|
-
roundTrip.Packages["Microsoft.Bcl.Memory"].Dlls);
|
|
85
|
-
Assert.AreEqual("3.11.0", roundTrip.Packages["Microsoft.CodeAnalysis.Analyzers"].Version);
|
|
86
|
-
Assert.AreEqual(0, roundTrip.Packages["Microsoft.CodeAnalysis.Analyzers"].Dlls.Count);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
[Test]
|
|
90
|
-
public void Save_CreatesInstallDirectoryIfMissing()
|
|
91
|
-
{
|
|
92
|
-
var nested = Path.Combine(_installPath, "nested-not-yet-created");
|
|
93
|
-
Assert.IsFalse(Directory.Exists(nested));
|
|
94
|
-
|
|
95
|
-
var manifest = new InstallManifest();
|
|
96
|
-
NuGetInstallManifest.Save(nested, manifest);
|
|
97
|
-
|
|
98
|
-
Assert.IsTrue(Directory.Exists(nested));
|
|
99
|
-
Assert.IsTrue(File.Exists(Path.Combine(nested, ".nuget-installed.json")));
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
[Test]
|
|
103
|
-
public void Load_ReturnsEmptyManifest_OnMalformedJson_AndDoesNotThrow()
|
|
104
|
-
{
|
|
105
|
-
File.WriteAllText(Path.Combine(_installPath, ".nuget-installed.json"), "{ not valid json");
|
|
106
|
-
|
|
107
|
-
var manifest = NuGetInstallManifest.Load(_installPath);
|
|
108
|
-
|
|
109
|
-
Assert.AreEqual(0, manifest.Packages.Count);
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
[Test]
|
|
113
|
-
public void Load_PreservesCaseInsensitiveLookup()
|
|
114
|
-
{
|
|
115
|
-
// The runtime resolver matches package IDs case-insensitively
|
|
116
|
-
// throughout. The manifest must round-trip with the same property.
|
|
117
|
-
var manifest = new InstallManifest();
|
|
118
|
-
manifest.Packages["System.Text.Json"] = new InstalledPackage("8.0.5");
|
|
119
|
-
NuGetInstallManifest.Save(_installPath, manifest);
|
|
120
|
-
|
|
121
|
-
var loaded = NuGetInstallManifest.Load(_installPath);
|
|
122
|
-
Assert.IsTrue(loaded.Packages.ContainsKey("system.text.json"),
|
|
123
|
-
"Package ID lookup must be case-insensitive after a round-trip.");
|
|
124
|
-
Assert.IsTrue(loaded.Packages.ContainsKey("SYSTEM.TEXT.JSON"));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
[Test]
|
|
128
|
-
public void Save_IsIdempotent_RoundTripIsByteForByte()
|
|
129
|
-
{
|
|
130
|
-
// Two saves of the same logical manifest must produce the exact
|
|
131
|
-
// same bytes — keeps git diffs clean and avoids spurious
|
|
132
|
-
// post-restore changes.
|
|
133
|
-
var manifest = new InstallManifest();
|
|
134
|
-
var entry = new InstalledPackage("8.0.5");
|
|
135
|
-
entry.Dlls.Add("System.Text.Json.8.0.5.dll");
|
|
136
|
-
manifest.Packages["System.Text.Json"] = entry;
|
|
137
|
-
|
|
138
|
-
NuGetInstallManifest.Save(_installPath, manifest);
|
|
139
|
-
var first = File.ReadAllBytes(Path.Combine(_installPath, ".nuget-installed.json"));
|
|
140
|
-
|
|
141
|
-
// Round-trip and save again.
|
|
142
|
-
var loaded = NuGetInstallManifest.Load(_installPath);
|
|
143
|
-
NuGetInstallManifest.Save(_installPath, loaded);
|
|
144
|
-
var second = File.ReadAllBytes(Path.Combine(_installPath, ".nuget-installed.json"));
|
|
145
|
-
|
|
146
|
-
CollectionAssert.AreEqual(first, second);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
[Test]
|
|
150
|
-
public void TryRebuildFromDisk_ReproducesSingleDllPackagesFromVersionedFilenames()
|
|
151
|
-
{
|
|
152
|
-
// Disaster recovery (#733 acceptance criterion): user deletes
|
|
153
|
-
// .nuget-installed.json. The next restore must rebuild the manifest
|
|
154
|
-
// from on-disk versioned filenames, with no re-extraction needed.
|
|
155
|
-
File.WriteAllText(Path.Combine(_installPath, "Microsoft.Extensions.Logging.8.0.1.dll"), "dummy");
|
|
156
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll"), "dummy");
|
|
157
|
-
File.WriteAllText(Path.Combine(_installPath, "R3.1.3.0.dll"), "dummy");
|
|
158
|
-
// Plus an unrelated file the rebuild must ignore.
|
|
159
|
-
File.WriteAllText(Path.Combine(_installPath, "ReadMe.txt"), "user notes");
|
|
160
|
-
|
|
161
|
-
var rebuilt = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
162
|
-
|
|
163
|
-
Assert.AreEqual(3, rebuilt.Packages.Count);
|
|
164
|
-
Assert.AreEqual("8.0.1", rebuilt.Packages["Microsoft.Extensions.Logging"].Version);
|
|
165
|
-
Assert.AreEqual("8.0.5", rebuilt.Packages["System.Text.Json"].Version);
|
|
166
|
-
Assert.AreEqual("1.3.0", rebuilt.Packages["R3"].Version);
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
[Test]
|
|
170
|
-
public void TryRebuildFromDisk_IgnoresLegacyUnversionedDllsAndNonDllFiles()
|
|
171
|
-
{
|
|
172
|
-
// Pre-flat-layout artifacts the user might still have on disk —
|
|
173
|
-
// the parser must reject them so the rebuild stays consistent.
|
|
174
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Memory.dll"), "legacy unversioned");
|
|
175
|
-
File.WriteAllText(Path.Combine(_installPath, "ReadMe.md"), "notes");
|
|
176
|
-
|
|
177
|
-
var rebuilt = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
178
|
-
|
|
179
|
-
Assert.AreEqual(0, rebuilt.Packages.Count);
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
[Test]
|
|
183
|
-
public void TryRebuildFromDisk_KeysMultiDllPackagesUnderSyntheticStems()
|
|
184
|
-
{
|
|
185
|
-
// Microsoft.Bcl.Memory is the canonical multi-DLL package: it ships
|
|
186
|
-
// System.Memory, System.Buffers, System.Runtime.CompilerServices.Unsafe.
|
|
187
|
-
// The disaster-recovery rebuild has no way to recover the real owner
|
|
188
|
-
// from filenames alone, so it MUST key those DLLs under their own
|
|
189
|
-
// stems as synthetic IDs. The follow-up MigrateSyntheticOwnerEntries
|
|
190
|
-
// call (driven from NuGetPackageInstaller.Install) reconciles the
|
|
191
|
-
// synthetic IDs back onto the real package ID — see the
|
|
192
|
-
// ReconcilesSyntheticEntries_ForMultiDllPackage test below.
|
|
193
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Memory.10.0.3.dll"), "dummy");
|
|
194
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Buffers.10.0.3.dll"), "dummy");
|
|
195
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll"), "dummy");
|
|
196
|
-
|
|
197
|
-
var rebuilt = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
198
|
-
|
|
199
|
-
Assert.AreEqual(3, rebuilt.Packages.Count);
|
|
200
|
-
Assert.IsTrue(rebuilt.Packages.ContainsKey("System.Memory"));
|
|
201
|
-
Assert.IsTrue(rebuilt.Packages.ContainsKey("System.Buffers"));
|
|
202
|
-
Assert.IsTrue(rebuilt.Packages.ContainsKey("System.Runtime.CompilerServices.Unsafe"));
|
|
203
|
-
Assert.IsFalse(rebuilt.Packages.ContainsKey("Microsoft.Bcl.Memory"),
|
|
204
|
-
"Filename-only rebuild cannot recover the real owning package id; the test guards the synthetic-id contract that MigrateSyntheticOwnerEntries depends on.");
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
[Test]
|
|
208
|
-
public void MigrateSyntheticOwnerEntries_ReconcilesMultiDllPackage_AndDoesNotTripCollisionCheck()
|
|
209
|
-
{
|
|
210
|
-
// End-to-end coverage of the post-rebuild reconciliation flow:
|
|
211
|
-
// 1. Seed a multi-DLL package's flat-layout DLLs without a manifest.
|
|
212
|
-
// 2. Run TryRebuildFromDisk — expect synthetic stem-keyed entries.
|
|
213
|
-
// 3. Run MigrateSyntheticOwnerEntries with the real package id and a
|
|
214
|
-
// planned-DLL list that matches the on-disk filenames.
|
|
215
|
-
// 4. Assert the synthetic entries are gone and the real package id
|
|
216
|
-
// now owns those same DLLs at the same version.
|
|
217
|
-
// Without this fix, NuGetPackageInstaller.Install would log
|
|
218
|
-
// "Refusing to install Microsoft.Bcl.Memory ..." and stick the user
|
|
219
|
-
// on the disaster-recovery path forever.
|
|
220
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Memory.10.0.3.dll"), "dummy");
|
|
221
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Buffers.10.0.3.dll"), "dummy");
|
|
222
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll"), "dummy");
|
|
223
|
-
|
|
224
|
-
var manifest = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
225
|
-
// Sanity: the rebuild produced synthetic entries (the [high] reproduction state).
|
|
226
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("System.Memory"));
|
|
227
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"));
|
|
228
|
-
|
|
229
|
-
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
230
|
-
{
|
|
231
|
-
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", Path.Combine(_installPath, "System.Memory.10.0.3.dll")),
|
|
232
|
-
new PlannedDll("lib/net8.0/System.Buffers.dll", "System.Buffers.10.0.3.dll", Path.Combine(_installPath, "System.Buffers.10.0.3.dll")),
|
|
233
|
-
new PlannedDll("lib/net8.0/System.Runtime.CompilerServices.Unsafe.dll", "System.Runtime.CompilerServices.Unsafe.10.0.3.dll", Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll")),
|
|
234
|
-
};
|
|
235
|
-
|
|
236
|
-
NuGetPackageInstaller.MigrateSyntheticOwnerEntries(manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
237
|
-
|
|
238
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("System.Memory"),
|
|
239
|
-
"Synthetic stem-keyed entry must be removed after reconciliation.");
|
|
240
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("System.Buffers"));
|
|
241
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("System.Runtime.CompilerServices.Unsafe"));
|
|
242
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"),
|
|
243
|
-
"Real package id must own the migrated DLLs after reconciliation.");
|
|
244
|
-
Assert.AreEqual("10.0.3", manifest.Packages["Microsoft.Bcl.Memory"].Version);
|
|
245
|
-
CollectionAssert.AreEquivalent(
|
|
246
|
-
new[]
|
|
247
|
-
{
|
|
248
|
-
"System.Memory.10.0.3.dll",
|
|
249
|
-
"System.Buffers.10.0.3.dll",
|
|
250
|
-
"System.Runtime.CompilerServices.Unsafe.10.0.3.dll",
|
|
251
|
-
},
|
|
252
|
-
manifest.Packages["Microsoft.Bcl.Memory"].Dlls);
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
[Test]
|
|
256
|
-
public void MigrateSyntheticOwnerEntries_DoesNotTouchUnrelatedPackages()
|
|
257
|
-
{
|
|
258
|
-
// Defense: the migrator must only strip entries whose DLL set is a
|
|
259
|
-
// subset of the planned filenames. A real distinct package that
|
|
260
|
-
// happens to share a version number must survive untouched.
|
|
261
|
-
var manifest = new InstallManifest();
|
|
262
|
-
var unrelatedEntry = new InstalledPackage("10.0.3");
|
|
263
|
-
unrelatedEntry.Dlls.Add("Newtonsoft.Json.10.0.3.dll");
|
|
264
|
-
manifest.Packages["Newtonsoft.Json"] = unrelatedEntry;
|
|
265
|
-
|
|
266
|
-
var syntheticEntry = new InstalledPackage("10.0.3");
|
|
267
|
-
syntheticEntry.Dlls.Add("System.Memory.10.0.3.dll");
|
|
268
|
-
manifest.Packages["System.Memory"] = syntheticEntry;
|
|
269
|
-
|
|
270
|
-
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
271
|
-
{
|
|
272
|
-
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", "/ignored/System.Memory.10.0.3.dll"),
|
|
273
|
-
};
|
|
274
|
-
|
|
275
|
-
NuGetPackageInstaller.MigrateSyntheticOwnerEntries(manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
276
|
-
|
|
277
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("Newtonsoft.Json"),
|
|
278
|
-
"Unrelated same-version package must not be migrated away.");
|
|
279
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("System.Memory"),
|
|
280
|
-
"Synthetic same-version entry whose DLLs match the planned set must be migrated.");
|
|
281
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"));
|
|
282
|
-
}
|
|
283
|
-
|
|
284
|
-
[Test]
|
|
285
|
-
public void MigrateSyntheticOwnerEntries_PartialDiskState_LeavesPlannedDllsMissingFromManifestEntry()
|
|
286
|
-
{
|
|
287
|
-
// Regression for the alreadyOnDisk gate: after MigrateSyntheticOwnerEntries
|
|
288
|
-
// pulls only the synthetic entries that the disaster-recovery rebuild saw on
|
|
289
|
-
// disk (a strict subset of the package's planned DLLs), the resulting
|
|
290
|
-
// real-owner entry must NOT advertise the missing DLLs. NuGetPackageInstaller
|
|
291
|
-
// gates `alreadyOnDisk` on `planned ⊆ manifestEntry.Dlls`, so an incomplete
|
|
292
|
-
// migration here MUST yield an entry whose Dlls set fails that superset
|
|
293
|
-
// check — otherwise extraction would be skipped and the missing files would
|
|
294
|
-
// never be re-extracted.
|
|
295
|
-
//
|
|
296
|
-
// Scenario: Microsoft.Bcl.Memory ships 3 DLLs; only 2 survived on disk
|
|
297
|
-
// (manifest deleted + one DLL lost to AV / partial cleanup). The user
|
|
298
|
-
// re-runs at the same package version. The post-migration manifest entry
|
|
299
|
-
// covers only the 2 surviving stems; planned still has 3.
|
|
300
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Memory.10.0.3.dll"), "dummy");
|
|
301
|
-
File.WriteAllText(Path.Combine(_installPath, "System.Buffers.10.0.3.dll"), "dummy");
|
|
302
|
-
// System.Runtime.CompilerServices.Unsafe.10.0.3.dll intentionally NOT seeded.
|
|
303
|
-
|
|
304
|
-
var manifest = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
305
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("System.Memory"));
|
|
306
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("System.Buffers"));
|
|
307
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("System.Runtime.CompilerServices.Unsafe"),
|
|
308
|
-
"Sanity: the missing DLL must not appear in the rebuilt manifest.");
|
|
309
|
-
|
|
310
|
-
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
311
|
-
{
|
|
312
|
-
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", Path.Combine(_installPath, "System.Memory.10.0.3.dll")),
|
|
313
|
-
new PlannedDll("lib/net8.0/System.Buffers.dll", "System.Buffers.10.0.3.dll", Path.Combine(_installPath, "System.Buffers.10.0.3.dll")),
|
|
314
|
-
new PlannedDll("lib/net8.0/System.Runtime.CompilerServices.Unsafe.dll", "System.Runtime.CompilerServices.Unsafe.10.0.3.dll", Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll")),
|
|
315
|
-
};
|
|
316
|
-
|
|
317
|
-
var migrated = NuGetPackageInstaller.MigrateSyntheticOwnerEntries(
|
|
318
|
-
manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
319
|
-
|
|
320
|
-
Assert.IsTrue(migrated, "Migration must signal that the manifest changed (caller must persist).");
|
|
321
|
-
|
|
322
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"));
|
|
323
|
-
var realEntry = manifest.Packages["Microsoft.Bcl.Memory"];
|
|
324
|
-
CollectionAssert.AreEquivalent(
|
|
325
|
-
new[] { "System.Memory.10.0.3.dll", "System.Buffers.10.0.3.dll" },
|
|
326
|
-
realEntry.Dlls,
|
|
327
|
-
"Migrated entry must reflect on-disk reality (2 of 3 DLLs), not the full planned set.");
|
|
328
|
-
|
|
329
|
-
// Cross-check the gate the installer uses: planned ⊄ realEntry.Dlls, so
|
|
330
|
-
// alreadyOnDisk would return false and the missing DLL gets re-extracted.
|
|
331
|
-
var allPlannedRecorded = planned.TrueForAll(
|
|
332
|
-
p => realEntry.Dlls.Contains(p.FileName, System.StringComparer.OrdinalIgnoreCase));
|
|
333
|
-
Assert.IsFalse(allPlannedRecorded,
|
|
334
|
-
"alreadyOnDisk gate would short-circuit incorrectly if the planned set were a subset of the migrated entry.");
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
[Test]
|
|
338
|
-
public void MigrateSyntheticOwnerEntries_ReturnsFalse_WhenNothingMigrated()
|
|
339
|
-
{
|
|
340
|
-
// Caller persists only on a true return — ensure we don't churn the manifest
|
|
341
|
-
// file when the migrator was a no-op.
|
|
342
|
-
var manifest = new InstallManifest();
|
|
343
|
-
var unrelatedEntry = new InstalledPackage("9.9.9");
|
|
344
|
-
unrelatedEntry.Dlls.Add("Unrelated.9.9.9.dll");
|
|
345
|
-
manifest.Packages["Unrelated"] = unrelatedEntry;
|
|
346
|
-
|
|
347
|
-
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
348
|
-
{
|
|
349
|
-
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", "/ignored/System.Memory.10.0.3.dll"),
|
|
350
|
-
};
|
|
351
|
-
|
|
352
|
-
var migrated = NuGetPackageInstaller.MigrateSyntheticOwnerEntries(
|
|
353
|
-
manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
354
|
-
|
|
355
|
-
Assert.IsFalse(migrated);
|
|
356
|
-
}
|
|
357
|
-
|
|
358
|
-
[Test]
|
|
359
|
-
public void MigrateSyntheticOwnerEntries_IgnoresEntriesAtDifferentVersion()
|
|
360
|
-
{
|
|
361
|
-
// A stem-keyed entry left at a DIFFERENT version is not part of the
|
|
362
|
-
// reconciliation scope; it gets cleaned up by the stale-version
|
|
363
|
-
// pass instead.
|
|
364
|
-
var manifest = new InstallManifest();
|
|
365
|
-
var olderSynthetic = new InstalledPackage("9.0.0");
|
|
366
|
-
olderSynthetic.Dlls.Add("System.Memory.9.0.0.dll");
|
|
367
|
-
manifest.Packages["System.Memory"] = olderSynthetic;
|
|
368
|
-
|
|
369
|
-
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
370
|
-
{
|
|
371
|
-
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", "/ignored/System.Memory.10.0.3.dll"),
|
|
372
|
-
};
|
|
373
|
-
|
|
374
|
-
NuGetPackageInstaller.MigrateSyntheticOwnerEntries(manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
375
|
-
|
|
376
|
-
Assert.IsTrue(manifest.Packages.ContainsKey("System.Memory"),
|
|
377
|
-
"Different-version synthetic entry must be left alone (stale-version cleanup handles it).");
|
|
378
|
-
Assert.IsFalse(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"),
|
|
379
|
-
"No real-owner entry should be created when nothing was migrated.");
|
|
380
|
-
}
|
|
381
|
-
|
|
382
|
-
[Test]
|
|
383
|
-
public void TryParseInstalledDllName_GreedilyConsumesEntireVersionTail()
|
|
384
|
-
{
|
|
385
|
-
// Regression check on the parser used by the disaster-recovery
|
|
386
|
-
// rebuild: for "System.Memory.10.0.3.dll" the version is "10.0.3"
|
|
387
|
-
// (not "0.3" or "3"), and the stem is "System.Memory".
|
|
388
|
-
Assert.IsTrue(NuGetInstallManifest.TryParseInstalledDllName(
|
|
389
|
-
"System.Memory.10.0.3.dll", out var stem, out var version));
|
|
390
|
-
Assert.AreEqual("System.Memory", stem);
|
|
391
|
-
Assert.AreEqual("10.0.3", version);
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
[Test]
|
|
395
|
-
public void TryParseInstalledDllName_HandlesPackageStemsWithDots()
|
|
396
|
-
{
|
|
397
|
-
Assert.IsTrue(NuGetInstallManifest.TryParseInstalledDllName(
|
|
398
|
-
"Microsoft.Extensions.Logging.Abstractions.8.0.3.dll", out var stem, out var version));
|
|
399
|
-
Assert.AreEqual("Microsoft.Extensions.Logging.Abstractions", stem);
|
|
400
|
-
Assert.AreEqual("8.0.3", version);
|
|
401
|
-
}
|
|
402
|
-
|
|
403
|
-
[Test]
|
|
404
|
-
public void TryParseInstalledDllName_HandlesShortStemAndLongVersion()
|
|
405
|
-
{
|
|
406
|
-
// "R3.1.3.0.dll" → stem "R3", version "1.3.0".
|
|
407
|
-
Assert.IsTrue(NuGetInstallManifest.TryParseInstalledDllName(
|
|
408
|
-
"R3.1.3.0.dll", out var stem, out var version));
|
|
409
|
-
Assert.AreEqual("R3", stem);
|
|
410
|
-
Assert.AreEqual("1.3.0", version);
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
[Test]
|
|
414
|
-
public void TryParseInstalledDllName_RejectsLegacyUnversionedFilename()
|
|
415
|
-
{
|
|
416
|
-
// No version tail → not a flat-layout install entry.
|
|
417
|
-
Assert.IsFalse(NuGetInstallManifest.TryParseInstalledDllName(
|
|
418
|
-
"System.Memory.dll", out _, out _));
|
|
419
|
-
}
|
|
420
|
-
|
|
421
|
-
[Test]
|
|
422
|
-
public void TryParseInstalledDllName_RejectsMalformedTail()
|
|
423
|
-
{
|
|
424
|
-
// ".bar" tail isn't a System.Version.
|
|
425
|
-
Assert.IsFalse(NuGetInstallManifest.TryParseInstalledDllName(
|
|
426
|
-
"Foo.bar.dll", out _, out _));
|
|
427
|
-
}
|
|
428
|
-
|
|
429
|
-
[Test]
|
|
430
|
-
public void TryParseInstalledDllName_RejectsNonDllExtension()
|
|
431
|
-
{
|
|
432
|
-
Assert.IsFalse(NuGetInstallManifest.TryParseInstalledDllName(
|
|
433
|
-
"System.Memory.10.0.3.exe", out _, out _));
|
|
434
|
-
}
|
|
435
|
-
}
|
|
436
|
-
}
|
|
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.Linq;
|
|
14
|
+
using NUnit.Framework;
|
|
15
|
+
using com.AtelierAI.Unity.Copilot.Editor.DependencyResolver;
|
|
16
|
+
|
|
17
|
+
namespace com.AtelierAI.Unity.Copilot.Editor.Tests.DependencyResolverTests
|
|
18
|
+
{
|
|
19
|
+
/// <summary>
|
|
20
|
+
/// Coverage for the on-disk manifest <c>.nuget-installed.json</c> introduced
|
|
21
|
+
/// for the flat layout (issue #733). The manifest is the primary source of
|
|
22
|
+
/// truth for "which DLL belongs to which package at which version", with
|
|
23
|
+
/// versioned-filename parsing as the disaster-recovery fallback.
|
|
24
|
+
/// </summary>
|
|
25
|
+
[TestFixture]
|
|
26
|
+
public class NuGetInstallManifestTests
|
|
27
|
+
{
|
|
28
|
+
string _installPath = string.Empty;
|
|
29
|
+
|
|
30
|
+
[SetUp]
|
|
31
|
+
public void SetUp()
|
|
32
|
+
{
|
|
33
|
+
_installPath = Path.Combine(
|
|
34
|
+
Path.GetTempPath(),
|
|
35
|
+
"Uco-Manifest-" + Path.GetRandomFileName());
|
|
36
|
+
Directory.CreateDirectory(_installPath);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
[TearDown]
|
|
40
|
+
public void TearDown()
|
|
41
|
+
{
|
|
42
|
+
if (Directory.Exists(_installPath))
|
|
43
|
+
{
|
|
44
|
+
try { Directory.Delete(_installPath, recursive: true); }
|
|
45
|
+
catch { /* best-effort cleanup */ }
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
[Test]
|
|
50
|
+
public void Load_ReturnsEmptyManifest_WhenFileMissing()
|
|
51
|
+
{
|
|
52
|
+
var manifest = NuGetInstallManifest.Load(_installPath);
|
|
53
|
+
Assert.AreEqual(0, manifest.Packages.Count);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
[Test]
|
|
57
|
+
public void SaveThenLoad_RoundTrips_AllFields()
|
|
58
|
+
{
|
|
59
|
+
var manifest = new InstallManifest();
|
|
60
|
+
var entry = new InstalledPackage("8.0.1");
|
|
61
|
+
entry.Dlls.Add("Microsoft.Extensions.Logging.8.0.1.dll");
|
|
62
|
+
entry.Dlls.Add("Microsoft.Extensions.Logging.Abstractions.8.0.3.dll");
|
|
63
|
+
manifest.Packages["Microsoft.Extensions.Logging"] = entry;
|
|
64
|
+
|
|
65
|
+
// Multi-DLL package with a different version.
|
|
66
|
+
var multi = new InstalledPackage("10.0.3");
|
|
67
|
+
multi.Dlls.Add("System.Memory.10.0.3.dll");
|
|
68
|
+
multi.Dlls.Add("System.Buffers.10.0.3.dll");
|
|
69
|
+
manifest.Packages["Microsoft.Bcl.Memory"] = multi;
|
|
70
|
+
|
|
71
|
+
// Empty-DLL entry (development-only dependency).
|
|
72
|
+
manifest.Packages["Microsoft.CodeAnalysis.Analyzers"] = new InstalledPackage("3.11.0");
|
|
73
|
+
|
|
74
|
+
NuGetInstallManifest.Save(_installPath, manifest);
|
|
75
|
+
|
|
76
|
+
var roundTrip = NuGetInstallManifest.Load(_installPath);
|
|
77
|
+
|
|
78
|
+
Assert.AreEqual(3, roundTrip.Packages.Count);
|
|
79
|
+
Assert.AreEqual("8.0.1", roundTrip.Packages["Microsoft.Extensions.Logging"].Version);
|
|
80
|
+
Assert.AreEqual(2, roundTrip.Packages["Microsoft.Extensions.Logging"].Dlls.Count);
|
|
81
|
+
Assert.AreEqual("10.0.3", roundTrip.Packages["Microsoft.Bcl.Memory"].Version);
|
|
82
|
+
CollectionAssert.AreEquivalent(
|
|
83
|
+
new[] { "System.Memory.10.0.3.dll", "System.Buffers.10.0.3.dll" },
|
|
84
|
+
roundTrip.Packages["Microsoft.Bcl.Memory"].Dlls);
|
|
85
|
+
Assert.AreEqual("3.11.0", roundTrip.Packages["Microsoft.CodeAnalysis.Analyzers"].Version);
|
|
86
|
+
Assert.AreEqual(0, roundTrip.Packages["Microsoft.CodeAnalysis.Analyzers"].Dlls.Count);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
[Test]
|
|
90
|
+
public void Save_CreatesInstallDirectoryIfMissing()
|
|
91
|
+
{
|
|
92
|
+
var nested = Path.Combine(_installPath, "nested-not-yet-created");
|
|
93
|
+
Assert.IsFalse(Directory.Exists(nested));
|
|
94
|
+
|
|
95
|
+
var manifest = new InstallManifest();
|
|
96
|
+
NuGetInstallManifest.Save(nested, manifest);
|
|
97
|
+
|
|
98
|
+
Assert.IsTrue(Directory.Exists(nested));
|
|
99
|
+
Assert.IsTrue(File.Exists(Path.Combine(nested, ".nuget-installed.json")));
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
[Test]
|
|
103
|
+
public void Load_ReturnsEmptyManifest_OnMalformedJson_AndDoesNotThrow()
|
|
104
|
+
{
|
|
105
|
+
File.WriteAllText(Path.Combine(_installPath, ".nuget-installed.json"), "{ not valid json");
|
|
106
|
+
|
|
107
|
+
var manifest = NuGetInstallManifest.Load(_installPath);
|
|
108
|
+
|
|
109
|
+
Assert.AreEqual(0, manifest.Packages.Count);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
[Test]
|
|
113
|
+
public void Load_PreservesCaseInsensitiveLookup()
|
|
114
|
+
{
|
|
115
|
+
// The runtime resolver matches package IDs case-insensitively
|
|
116
|
+
// throughout. The manifest must round-trip with the same property.
|
|
117
|
+
var manifest = new InstallManifest();
|
|
118
|
+
manifest.Packages["System.Text.Json"] = new InstalledPackage("8.0.5");
|
|
119
|
+
NuGetInstallManifest.Save(_installPath, manifest);
|
|
120
|
+
|
|
121
|
+
var loaded = NuGetInstallManifest.Load(_installPath);
|
|
122
|
+
Assert.IsTrue(loaded.Packages.ContainsKey("system.text.json"),
|
|
123
|
+
"Package ID lookup must be case-insensitive after a round-trip.");
|
|
124
|
+
Assert.IsTrue(loaded.Packages.ContainsKey("SYSTEM.TEXT.JSON"));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
[Test]
|
|
128
|
+
public void Save_IsIdempotent_RoundTripIsByteForByte()
|
|
129
|
+
{
|
|
130
|
+
// Two saves of the same logical manifest must produce the exact
|
|
131
|
+
// same bytes — keeps git diffs clean and avoids spurious
|
|
132
|
+
// post-restore changes.
|
|
133
|
+
var manifest = new InstallManifest();
|
|
134
|
+
var entry = new InstalledPackage("8.0.5");
|
|
135
|
+
entry.Dlls.Add("System.Text.Json.8.0.5.dll");
|
|
136
|
+
manifest.Packages["System.Text.Json"] = entry;
|
|
137
|
+
|
|
138
|
+
NuGetInstallManifest.Save(_installPath, manifest);
|
|
139
|
+
var first = File.ReadAllBytes(Path.Combine(_installPath, ".nuget-installed.json"));
|
|
140
|
+
|
|
141
|
+
// Round-trip and save again.
|
|
142
|
+
var loaded = NuGetInstallManifest.Load(_installPath);
|
|
143
|
+
NuGetInstallManifest.Save(_installPath, loaded);
|
|
144
|
+
var second = File.ReadAllBytes(Path.Combine(_installPath, ".nuget-installed.json"));
|
|
145
|
+
|
|
146
|
+
CollectionAssert.AreEqual(first, second);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
[Test]
|
|
150
|
+
public void TryRebuildFromDisk_ReproducesSingleDllPackagesFromVersionedFilenames()
|
|
151
|
+
{
|
|
152
|
+
// Disaster recovery (#733 acceptance criterion): user deletes
|
|
153
|
+
// .nuget-installed.json. The next restore must rebuild the manifest
|
|
154
|
+
// from on-disk versioned filenames, with no re-extraction needed.
|
|
155
|
+
File.WriteAllText(Path.Combine(_installPath, "Microsoft.Extensions.Logging.8.0.1.dll"), "dummy");
|
|
156
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Text.Json.8.0.5.dll"), "dummy");
|
|
157
|
+
File.WriteAllText(Path.Combine(_installPath, "R3.1.3.0.dll"), "dummy");
|
|
158
|
+
// Plus an unrelated file the rebuild must ignore.
|
|
159
|
+
File.WriteAllText(Path.Combine(_installPath, "ReadMe.txt"), "user notes");
|
|
160
|
+
|
|
161
|
+
var rebuilt = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
162
|
+
|
|
163
|
+
Assert.AreEqual(3, rebuilt.Packages.Count);
|
|
164
|
+
Assert.AreEqual("8.0.1", rebuilt.Packages["Microsoft.Extensions.Logging"].Version);
|
|
165
|
+
Assert.AreEqual("8.0.5", rebuilt.Packages["System.Text.Json"].Version);
|
|
166
|
+
Assert.AreEqual("1.3.0", rebuilt.Packages["R3"].Version);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
[Test]
|
|
170
|
+
public void TryRebuildFromDisk_IgnoresLegacyUnversionedDllsAndNonDllFiles()
|
|
171
|
+
{
|
|
172
|
+
// Pre-flat-layout artifacts the user might still have on disk —
|
|
173
|
+
// the parser must reject them so the rebuild stays consistent.
|
|
174
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Memory.dll"), "legacy unversioned");
|
|
175
|
+
File.WriteAllText(Path.Combine(_installPath, "ReadMe.md"), "notes");
|
|
176
|
+
|
|
177
|
+
var rebuilt = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
178
|
+
|
|
179
|
+
Assert.AreEqual(0, rebuilt.Packages.Count);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
[Test]
|
|
183
|
+
public void TryRebuildFromDisk_KeysMultiDllPackagesUnderSyntheticStems()
|
|
184
|
+
{
|
|
185
|
+
// Microsoft.Bcl.Memory is the canonical multi-DLL package: it ships
|
|
186
|
+
// System.Memory, System.Buffers, System.Runtime.CompilerServices.Unsafe.
|
|
187
|
+
// The disaster-recovery rebuild has no way to recover the real owner
|
|
188
|
+
// from filenames alone, so it MUST key those DLLs under their own
|
|
189
|
+
// stems as synthetic IDs. The follow-up MigrateSyntheticOwnerEntries
|
|
190
|
+
// call (driven from NuGetPackageInstaller.Install) reconciles the
|
|
191
|
+
// synthetic IDs back onto the real package ID — see the
|
|
192
|
+
// ReconcilesSyntheticEntries_ForMultiDllPackage test below.
|
|
193
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Memory.10.0.3.dll"), "dummy");
|
|
194
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Buffers.10.0.3.dll"), "dummy");
|
|
195
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll"), "dummy");
|
|
196
|
+
|
|
197
|
+
var rebuilt = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
198
|
+
|
|
199
|
+
Assert.AreEqual(3, rebuilt.Packages.Count);
|
|
200
|
+
Assert.IsTrue(rebuilt.Packages.ContainsKey("System.Memory"));
|
|
201
|
+
Assert.IsTrue(rebuilt.Packages.ContainsKey("System.Buffers"));
|
|
202
|
+
Assert.IsTrue(rebuilt.Packages.ContainsKey("System.Runtime.CompilerServices.Unsafe"));
|
|
203
|
+
Assert.IsFalse(rebuilt.Packages.ContainsKey("Microsoft.Bcl.Memory"),
|
|
204
|
+
"Filename-only rebuild cannot recover the real owning package id; the test guards the synthetic-id contract that MigrateSyntheticOwnerEntries depends on.");
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
[Test]
|
|
208
|
+
public void MigrateSyntheticOwnerEntries_ReconcilesMultiDllPackage_AndDoesNotTripCollisionCheck()
|
|
209
|
+
{
|
|
210
|
+
// End-to-end coverage of the post-rebuild reconciliation flow:
|
|
211
|
+
// 1. Seed a multi-DLL package's flat-layout DLLs without a manifest.
|
|
212
|
+
// 2. Run TryRebuildFromDisk — expect synthetic stem-keyed entries.
|
|
213
|
+
// 3. Run MigrateSyntheticOwnerEntries with the real package id and a
|
|
214
|
+
// planned-DLL list that matches the on-disk filenames.
|
|
215
|
+
// 4. Assert the synthetic entries are gone and the real package id
|
|
216
|
+
// now owns those same DLLs at the same version.
|
|
217
|
+
// Without this fix, NuGetPackageInstaller.Install would log
|
|
218
|
+
// "Refusing to install Microsoft.Bcl.Memory ..." and stick the user
|
|
219
|
+
// on the disaster-recovery path forever.
|
|
220
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Memory.10.0.3.dll"), "dummy");
|
|
221
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Buffers.10.0.3.dll"), "dummy");
|
|
222
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll"), "dummy");
|
|
223
|
+
|
|
224
|
+
var manifest = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
225
|
+
// Sanity: the rebuild produced synthetic entries (the [high] reproduction state).
|
|
226
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("System.Memory"));
|
|
227
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"));
|
|
228
|
+
|
|
229
|
+
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
230
|
+
{
|
|
231
|
+
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", Path.Combine(_installPath, "System.Memory.10.0.3.dll")),
|
|
232
|
+
new PlannedDll("lib/net8.0/System.Buffers.dll", "System.Buffers.10.0.3.dll", Path.Combine(_installPath, "System.Buffers.10.0.3.dll")),
|
|
233
|
+
new PlannedDll("lib/net8.0/System.Runtime.CompilerServices.Unsafe.dll", "System.Runtime.CompilerServices.Unsafe.10.0.3.dll", Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll")),
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
NuGetPackageInstaller.MigrateSyntheticOwnerEntries(manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
237
|
+
|
|
238
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("System.Memory"),
|
|
239
|
+
"Synthetic stem-keyed entry must be removed after reconciliation.");
|
|
240
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("System.Buffers"));
|
|
241
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("System.Runtime.CompilerServices.Unsafe"));
|
|
242
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"),
|
|
243
|
+
"Real package id must own the migrated DLLs after reconciliation.");
|
|
244
|
+
Assert.AreEqual("10.0.3", manifest.Packages["Microsoft.Bcl.Memory"].Version);
|
|
245
|
+
CollectionAssert.AreEquivalent(
|
|
246
|
+
new[]
|
|
247
|
+
{
|
|
248
|
+
"System.Memory.10.0.3.dll",
|
|
249
|
+
"System.Buffers.10.0.3.dll",
|
|
250
|
+
"System.Runtime.CompilerServices.Unsafe.10.0.3.dll",
|
|
251
|
+
},
|
|
252
|
+
manifest.Packages["Microsoft.Bcl.Memory"].Dlls);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
[Test]
|
|
256
|
+
public void MigrateSyntheticOwnerEntries_DoesNotTouchUnrelatedPackages()
|
|
257
|
+
{
|
|
258
|
+
// Defense: the migrator must only strip entries whose DLL set is a
|
|
259
|
+
// subset of the planned filenames. A real distinct package that
|
|
260
|
+
// happens to share a version number must survive untouched.
|
|
261
|
+
var manifest = new InstallManifest();
|
|
262
|
+
var unrelatedEntry = new InstalledPackage("10.0.3");
|
|
263
|
+
unrelatedEntry.Dlls.Add("Newtonsoft.Json.10.0.3.dll");
|
|
264
|
+
manifest.Packages["Newtonsoft.Json"] = unrelatedEntry;
|
|
265
|
+
|
|
266
|
+
var syntheticEntry = new InstalledPackage("10.0.3");
|
|
267
|
+
syntheticEntry.Dlls.Add("System.Memory.10.0.3.dll");
|
|
268
|
+
manifest.Packages["System.Memory"] = syntheticEntry;
|
|
269
|
+
|
|
270
|
+
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
271
|
+
{
|
|
272
|
+
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", "/ignored/System.Memory.10.0.3.dll"),
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
NuGetPackageInstaller.MigrateSyntheticOwnerEntries(manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
276
|
+
|
|
277
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("Newtonsoft.Json"),
|
|
278
|
+
"Unrelated same-version package must not be migrated away.");
|
|
279
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("System.Memory"),
|
|
280
|
+
"Synthetic same-version entry whose DLLs match the planned set must be migrated.");
|
|
281
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"));
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
[Test]
|
|
285
|
+
public void MigrateSyntheticOwnerEntries_PartialDiskState_LeavesPlannedDllsMissingFromManifestEntry()
|
|
286
|
+
{
|
|
287
|
+
// Regression for the alreadyOnDisk gate: after MigrateSyntheticOwnerEntries
|
|
288
|
+
// pulls only the synthetic entries that the disaster-recovery rebuild saw on
|
|
289
|
+
// disk (a strict subset of the package's planned DLLs), the resulting
|
|
290
|
+
// real-owner entry must NOT advertise the missing DLLs. NuGetPackageInstaller
|
|
291
|
+
// gates `alreadyOnDisk` on `planned ⊆ manifestEntry.Dlls`, so an incomplete
|
|
292
|
+
// migration here MUST yield an entry whose Dlls set fails that superset
|
|
293
|
+
// check — otherwise extraction would be skipped and the missing files would
|
|
294
|
+
// never be re-extracted.
|
|
295
|
+
//
|
|
296
|
+
// Scenario: Microsoft.Bcl.Memory ships 3 DLLs; only 2 survived on disk
|
|
297
|
+
// (manifest deleted + one DLL lost to AV / partial cleanup). The user
|
|
298
|
+
// re-runs at the same package version. The post-migration manifest entry
|
|
299
|
+
// covers only the 2 surviving stems; planned still has 3.
|
|
300
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Memory.10.0.3.dll"), "dummy");
|
|
301
|
+
File.WriteAllText(Path.Combine(_installPath, "System.Buffers.10.0.3.dll"), "dummy");
|
|
302
|
+
// System.Runtime.CompilerServices.Unsafe.10.0.3.dll intentionally NOT seeded.
|
|
303
|
+
|
|
304
|
+
var manifest = NuGetInstallManifest.TryRebuildFromDisk(_installPath);
|
|
305
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("System.Memory"));
|
|
306
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("System.Buffers"));
|
|
307
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("System.Runtime.CompilerServices.Unsafe"),
|
|
308
|
+
"Sanity: the missing DLL must not appear in the rebuilt manifest.");
|
|
309
|
+
|
|
310
|
+
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
311
|
+
{
|
|
312
|
+
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", Path.Combine(_installPath, "System.Memory.10.0.3.dll")),
|
|
313
|
+
new PlannedDll("lib/net8.0/System.Buffers.dll", "System.Buffers.10.0.3.dll", Path.Combine(_installPath, "System.Buffers.10.0.3.dll")),
|
|
314
|
+
new PlannedDll("lib/net8.0/System.Runtime.CompilerServices.Unsafe.dll", "System.Runtime.CompilerServices.Unsafe.10.0.3.dll", Path.Combine(_installPath, "System.Runtime.CompilerServices.Unsafe.10.0.3.dll")),
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
var migrated = NuGetPackageInstaller.MigrateSyntheticOwnerEntries(
|
|
318
|
+
manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
319
|
+
|
|
320
|
+
Assert.IsTrue(migrated, "Migration must signal that the manifest changed (caller must persist).");
|
|
321
|
+
|
|
322
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"));
|
|
323
|
+
var realEntry = manifest.Packages["Microsoft.Bcl.Memory"];
|
|
324
|
+
CollectionAssert.AreEquivalent(
|
|
325
|
+
new[] { "System.Memory.10.0.3.dll", "System.Buffers.10.0.3.dll" },
|
|
326
|
+
realEntry.Dlls,
|
|
327
|
+
"Migrated entry must reflect on-disk reality (2 of 3 DLLs), not the full planned set.");
|
|
328
|
+
|
|
329
|
+
// Cross-check the gate the installer uses: planned ⊄ realEntry.Dlls, so
|
|
330
|
+
// alreadyOnDisk would return false and the missing DLL gets re-extracted.
|
|
331
|
+
var allPlannedRecorded = planned.TrueForAll(
|
|
332
|
+
p => realEntry.Dlls.Contains(p.FileName, System.StringComparer.OrdinalIgnoreCase));
|
|
333
|
+
Assert.IsFalse(allPlannedRecorded,
|
|
334
|
+
"alreadyOnDisk gate would short-circuit incorrectly if the planned set were a subset of the migrated entry.");
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
[Test]
|
|
338
|
+
public void MigrateSyntheticOwnerEntries_ReturnsFalse_WhenNothingMigrated()
|
|
339
|
+
{
|
|
340
|
+
// Caller persists only on a true return — ensure we don't churn the manifest
|
|
341
|
+
// file when the migrator was a no-op.
|
|
342
|
+
var manifest = new InstallManifest();
|
|
343
|
+
var unrelatedEntry = new InstalledPackage("9.9.9");
|
|
344
|
+
unrelatedEntry.Dlls.Add("Unrelated.9.9.9.dll");
|
|
345
|
+
manifest.Packages["Unrelated"] = unrelatedEntry;
|
|
346
|
+
|
|
347
|
+
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
348
|
+
{
|
|
349
|
+
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", "/ignored/System.Memory.10.0.3.dll"),
|
|
350
|
+
};
|
|
351
|
+
|
|
352
|
+
var migrated = NuGetPackageInstaller.MigrateSyntheticOwnerEntries(
|
|
353
|
+
manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
354
|
+
|
|
355
|
+
Assert.IsFalse(migrated);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
[Test]
|
|
359
|
+
public void MigrateSyntheticOwnerEntries_IgnoresEntriesAtDifferentVersion()
|
|
360
|
+
{
|
|
361
|
+
// A stem-keyed entry left at a DIFFERENT version is not part of the
|
|
362
|
+
// reconciliation scope; it gets cleaned up by the stale-version
|
|
363
|
+
// pass instead.
|
|
364
|
+
var manifest = new InstallManifest();
|
|
365
|
+
var olderSynthetic = new InstalledPackage("9.0.0");
|
|
366
|
+
olderSynthetic.Dlls.Add("System.Memory.9.0.0.dll");
|
|
367
|
+
manifest.Packages["System.Memory"] = olderSynthetic;
|
|
368
|
+
|
|
369
|
+
var planned = new System.Collections.Generic.List<PlannedDll>
|
|
370
|
+
{
|
|
371
|
+
new PlannedDll("lib/net8.0/System.Memory.dll", "System.Memory.10.0.3.dll", "/ignored/System.Memory.10.0.3.dll"),
|
|
372
|
+
};
|
|
373
|
+
|
|
374
|
+
NuGetPackageInstaller.MigrateSyntheticOwnerEntries(manifest, "Microsoft.Bcl.Memory", "10.0.3", planned);
|
|
375
|
+
|
|
376
|
+
Assert.IsTrue(manifest.Packages.ContainsKey("System.Memory"),
|
|
377
|
+
"Different-version synthetic entry must be left alone (stale-version cleanup handles it).");
|
|
378
|
+
Assert.IsFalse(manifest.Packages.ContainsKey("Microsoft.Bcl.Memory"),
|
|
379
|
+
"No real-owner entry should be created when nothing was migrated.");
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
[Test]
|
|
383
|
+
public void TryParseInstalledDllName_GreedilyConsumesEntireVersionTail()
|
|
384
|
+
{
|
|
385
|
+
// Regression check on the parser used by the disaster-recovery
|
|
386
|
+
// rebuild: for "System.Memory.10.0.3.dll" the version is "10.0.3"
|
|
387
|
+
// (not "0.3" or "3"), and the stem is "System.Memory".
|
|
388
|
+
Assert.IsTrue(NuGetInstallManifest.TryParseInstalledDllName(
|
|
389
|
+
"System.Memory.10.0.3.dll", out var stem, out var version));
|
|
390
|
+
Assert.AreEqual("System.Memory", stem);
|
|
391
|
+
Assert.AreEqual("10.0.3", version);
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
[Test]
|
|
395
|
+
public void TryParseInstalledDllName_HandlesPackageStemsWithDots()
|
|
396
|
+
{
|
|
397
|
+
Assert.IsTrue(NuGetInstallManifest.TryParseInstalledDllName(
|
|
398
|
+
"Microsoft.Extensions.Logging.Abstractions.8.0.3.dll", out var stem, out var version));
|
|
399
|
+
Assert.AreEqual("Microsoft.Extensions.Logging.Abstractions", stem);
|
|
400
|
+
Assert.AreEqual("8.0.3", version);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
[Test]
|
|
404
|
+
public void TryParseInstalledDllName_HandlesShortStemAndLongVersion()
|
|
405
|
+
{
|
|
406
|
+
// "R3.1.3.0.dll" → stem "R3", version "1.3.0".
|
|
407
|
+
Assert.IsTrue(NuGetInstallManifest.TryParseInstalledDllName(
|
|
408
|
+
"R3.1.3.0.dll", out var stem, out var version));
|
|
409
|
+
Assert.AreEqual("R3", stem);
|
|
410
|
+
Assert.AreEqual("1.3.0", version);
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
[Test]
|
|
414
|
+
public void TryParseInstalledDllName_RejectsLegacyUnversionedFilename()
|
|
415
|
+
{
|
|
416
|
+
// No version tail → not a flat-layout install entry.
|
|
417
|
+
Assert.IsFalse(NuGetInstallManifest.TryParseInstalledDllName(
|
|
418
|
+
"System.Memory.dll", out _, out _));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
[Test]
|
|
422
|
+
public void TryParseInstalledDllName_RejectsMalformedTail()
|
|
423
|
+
{
|
|
424
|
+
// ".bar" tail isn't a System.Version.
|
|
425
|
+
Assert.IsFalse(NuGetInstallManifest.TryParseInstalledDllName(
|
|
426
|
+
"Foo.bar.dll", out _, out _));
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
[Test]
|
|
430
|
+
public void TryParseInstalledDllName_RejectsNonDllExtension()
|
|
431
|
+
{
|
|
432
|
+
Assert.IsFalse(NuGetInstallManifest.TryParseInstalledDllName(
|
|
433
|
+
"System.Memory.10.0.3.exe", out _, out _));
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|