Skip to content

Commit e83630d

Browse files
nagilsondsplaisted
authored andcommitted
Respond to dnup feedback from prototype code review
1 parent 181e526 commit e83630d

File tree

4 files changed

+76
-306
lines changed

4 files changed

+76
-306
lines changed

src/Installer/Microsoft.Dotnet.Installation/Internal/ArchiveDotnetExtractor.cs

Lines changed: 3 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -62,30 +62,6 @@ private void VerifyArchive(string archivePath)
6262
}
6363
}
6464

65-
66-
67-
internal static string ConstructArchiveName(string? versionString, string rid, string suffix)
68-
{
69-
// If version is not specified, use a generic name
70-
if (string.IsNullOrEmpty(versionString))
71-
{
72-
return $"dotnet-sdk-{rid}{suffix}";
73-
}
74-
75-
// Make sure the version string doesn't have any build hash or prerelease identifiers
76-
// This ensures compatibility with the official download URLs
77-
string cleanVersion = versionString;
78-
int dashIndex = versionString.IndexOf('-');
79-
if (dashIndex >= 0)
80-
{
81-
cleanVersion = versionString.Substring(0, dashIndex);
82-
}
83-
84-
return $"dotnet-sdk-{cleanVersion}-{rid}{suffix}";
85-
}
86-
87-
88-
8965
public void Commit()
9066
{
9167
Commit(GetExistingSdkVersions(_request.InstallRoot));
@@ -205,9 +181,7 @@ private string DecompressTarGzIfNeeded(string archivePath, out bool needsDecompr
205181
return archivePath;
206182
}
207183

208-
string decompressedPath = Path.Combine(
209-
Path.GetDirectoryName(archivePath) ?? Directory.CreateTempSubdirectory().FullName,
210-
"decompressed.tar");
184+
string decompressedPath = archivePath.Replace(".gz", "");
211185

212186
using FileStream originalFileStream = File.OpenRead(archivePath);
213187
using FileStream decompressedFileStream = File.Create(decompressedPath);
@@ -293,7 +267,7 @@ private void ExtractTarFileEntry(TarEntry entry, string targetDir, MuxerHandling
293267
private void HandleMuxerUpdateFromTar(TarEntry entry, string muxerTargetPath)
294268
{
295269
// Create a temporary file for the muxer first to avoid locking issues
296-
var tempMuxerPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
270+
var tempMuxerPath = Directory.CreateTempSubdirectory().FullName;
297271
using (var outStream = File.Create(tempMuxerPath))
298272
{
299273
entry.DataStream?.CopyTo(outStream);
@@ -378,7 +352,7 @@ private void ExtractZipEntry(ZipArchiveEntry entry, string targetDir, MuxerHandl
378352
*/
379353
private void HandleMuxerUpdateFromZip(ZipArchiveEntry entry, string muxerTargetPath)
380354
{
381-
var tempMuxerPath = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName());
355+
var tempMuxerPath = Directory.CreateTempSubdirectory().FullName;
382356
entry.ExtractToFile(tempMuxerPath, overwrite: true);
383357

384358
try

src/Installer/dnup/DnupSharedManifest.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@ private string GetManifestPath()
4949

5050
// Default location
5151
return Path.Combine(
52-
Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
52+
RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData) : Environment.GetFolderPath(Environment.SpecialFolder.UserProfile),
5353
"dnup",
5454
"dnup_manifest.json");
5555
}

test/dnup.Tests/DnupE2Etest.cs

Lines changed: 36 additions & 99 deletions
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,10 @@
88
using System.Threading.Tasks;
99
using FluentAssertions;
1010
using Microsoft.Deployment.DotNet.Releases;
11-
using Microsoft.Dotnet.Installation;
1211
using Microsoft.DotNet.Tools.Bootstrapper;
1312
using Microsoft.DotNet.Tools.Dnup.Tests.Utilities;
13+
using Microsoft.Dotnet.Installation;
1414
using Xunit;
15-
using Microsoft.Dotnet.Installation.Internal;
1615

1716
namespace Microsoft.DotNet.Tools.Dnup.Tests;
1817

@@ -50,7 +49,7 @@ public void Test(string channel)
5049
var updateChannel = new UpdateChannel(channel);
5150
var expectedVersion = new ManifestChannelVersionResolver().Resolve(
5251
new DotnetInstallRequest(
53-
new DotnetInstallRoot(testEnv.InstallPath, InstallerUtilities.GetDefaultInstallArchitecture()),
52+
new DotnetInstallRoot(testEnv.InstallPath, DnupUtilities.GetDefaultInstallArchitecture()),
5453
updateChannel,
5554
InstallComponent.SDK,
5655
new InstallRequestOptions()));
@@ -60,17 +59,15 @@ public void Test(string channel)
6059
Console.WriteLine($"Channel '{channel}' resolved to version: {expectedVersion}");
6160

6261
// Execute the command with explicit manifest path as a separate process
63-
var args = DnupTestUtilities.BuildArguments(channel, testEnv.InstallPath, testEnv.ManifestPath);
64-
65-
DnupProcessResult result = DnupTestUtilities.RunDnupProcess(args, captureOutput: true, workingDirectory: testEnv.TempRoot);
66-
result.ExitCode.Should().Be(0,
67-
$"dnup exited with code {result.ExitCode}. Output:\n{DnupTestUtilities.FormatOutputForAssertions(result)}");
62+
var args = DnupTestUtilities.BuildArguments(channel, testEnv.InstallPath, testEnv.ManifestPath);
63+
(int exitCode, string output) = DnupTestUtilities.RunDnupProcess(args, captureOutput: true, workingDirectory: testEnv.TempRoot);
64+
exitCode.Should().Be(0, $"dnup exited with code {exitCode}. Output:\n{output}");
6865

6966
Directory.Exists(testEnv.InstallPath).Should().BeTrue();
7067
Directory.Exists(Path.GetDirectoryName(testEnv.ManifestPath)).Should().BeTrue();
7168

7269
// Verify the installation was properly recorded in the manifest
73-
List<DotnetInstall> installs = [];
70+
List<DotnetInstall> installs = new();
7471
using (var finalizeLock = new ScopedMutex(Constants.MutexNames.ModifyInstallationStates))
7572
{
7673
var manifest = new DnupSharedManifest(testEnv.ManifestPath);
@@ -83,10 +80,7 @@ public void Test(string channel)
8380
matchingInstalls.Should().ContainSingle();
8481

8582
var install = matchingInstalls[0];
86-
install.Component.Should().Be(InstallComponent.SDK);
87-
88-
DnupTestUtilities.ValidateInstall(install).Should().BeTrue(
89-
$"ArchiveInstallationValidator failed for installed version {install.Version} at {testEnv.InstallPath}");
83+
install.Component.Should().Be(Microsoft.Dotnet.Installation.InstallComponent.SDK);
9084

9185
// Verify the installed version matches what the resolver predicted
9286
if (!updateChannel.IsFullySpecifiedVersion())
@@ -126,36 +120,31 @@ public void TestReusesExistingInstall()
126120

127121
// Execute dnup to install the SDK the first time with explicit manifest path as a separate process
128122
Console.WriteLine($"First installation of {channel}");
129-
DnupProcessResult firstInstall = DnupTestUtilities.RunDnupProcess(args, captureOutput: true, workingDirectory: testEnv.TempRoot);
130-
firstInstall.ExitCode.Should().Be(0,
131-
$"First installation failed with exit code {firstInstall.ExitCode}. Output:\n{DnupTestUtilities.FormatOutputForAssertions(firstInstall)}");
123+
(int exitCode, string firstInstallOutput) = DnupTestUtilities.RunDnupProcess(args, captureOutput: true, workingDirectory: testEnv.TempRoot);
124+
exitCode.Should().Be(0, $"First installation failed with exit code {exitCode}. Output:\n{firstInstallOutput}");
132125

133-
List<DotnetInstall> firstDnupInstalls = [];
126+
List<DotnetInstall> firstDnupInstalls = new();
134127
// Verify the installation was successful
135128
using (var finalizeLock = new ScopedMutex(Constants.MutexNames.ModifyInstallationStates))
136129
{
137130
var manifest = new DnupSharedManifest(testEnv.ManifestPath);
138131
firstDnupInstalls = manifest.GetInstalledVersions().ToList();
139132
}
140133

141-
var firstInstallRecord = firstDnupInstalls.Single(i => DnupUtilities.PathsEqual(i.InstallRoot.Path, testEnv.InstallPath));
142-
DnupTestUtilities.ValidateInstall(firstInstallRecord).Should().BeTrue(
143-
$"ArchiveInstallationValidator failed for initial install of {channel} at {testEnv.InstallPath}");
134+
firstDnupInstalls.Where(i => DnupUtilities.PathsEqual(i.InstallRoot.Path, testEnv.InstallPath)).Should().ContainSingle();
144135

145136
// Now install the same SDK again and capture the console output
146137
Console.WriteLine($"Installing .NET SDK {channel} again (should be skipped)");
147-
DnupProcessResult secondInstall = DnupTestUtilities.RunDnupProcess(args, captureOutput: true, workingDirectory: testEnv.TempRoot);
148-
secondInstall.ExitCode.Should().Be(0,
149-
$"Second installation failed with exit code {secondInstall.ExitCode}. Output:\n{DnupTestUtilities.FormatOutputForAssertions(secondInstall)}");
138+
(exitCode, string output) = DnupTestUtilities.RunDnupProcess(args, captureOutput: true, workingDirectory: testEnv.TempRoot);
139+
exitCode.Should().Be(0, $"Second installation failed with exit code {exitCode}. Output:\n{output}");
150140

151-
DnupTestUtilities.AssertOutput(secondInstall, output =>
152-
{
153-
output.Should().Contain("is already installed, skipping installation",
154-
"dnup should detect that the SDK is already installed and skip the installation");
141+
// Verify the output contains a message indicating the SDK is already installed
142+
output.Should().Contain("is already installed, skipping installation",
143+
"dnup should detect that the SDK is already installed and skip the installation");
155144

156-
output.Should().NotContain("Downloading .NET SDK",
157-
"dnup should not attempt to download the SDK again");
158-
});
145+
// The output should not contain download progress
146+
output.Should().NotContain("Downloading .NET SDK",
147+
"dnup should not attempt to download the SDK again");
159148

160149
List<DotnetInstall> matchingInstalls = new();
161150
// Verify the installation record in the manifest hasn't changed
@@ -170,8 +159,6 @@ public void TestReusesExistingInstall()
170159
matchingInstalls.Should().ContainSingle();
171160
// And it should be for the specified version
172161
matchingInstalls[0].Version.ToString().Should().Be(channel);
173-
DnupTestUtilities.ValidateInstall(matchingInstalls[0]).Should().BeTrue(
174-
$"ArchiveInstallationValidator failed after reinstall attempt for {channel} at {testEnv.InstallPath}");
175162
}
176163
}
177164

@@ -181,82 +168,32 @@ public void TestReusesExistingInstall()
181168
[Collection("DnupConcurrencyCollection")]
182169
public class ConcurrentInstallationTests
183170
{
184-
public static IEnumerable<object[]> ConcurrentInstallChannels => new List<object[]>
171+
[Fact]
172+
public async Task ConcurrentInstallsSerializeViaGlobalMutex()
185173
{
186-
new object[] { "9.0.103", "9.0.103", false },
187-
new object[] { "9.0.103", "preview", true }
188-
};
174+
const string channel = "9.0.103";
189175

190-
[Theory]
191-
[MemberData(nameof(ConcurrentInstallChannels))]
192-
public async Task ConcurrentInstallsSerializeViaGlobalMutex(string firstChannel, string secondChannel, bool expectDistinct)
193-
{
194176
using var testEnv = DnupTestUtilities.CreateTestEnvironment();
177+
var args1 = DnupTestUtilities.BuildArguments(channel, testEnv.InstallPath, testEnv.ManifestPath);
178+
var args2 = DnupTestUtilities.BuildArguments(channel, testEnv.InstallPath, testEnv.ManifestPath);
195179

196-
var resolver = new ManifestChannelVersionResolver();
197-
ReleaseVersion? firstResolved = resolver.Resolve(
198-
new DotnetInstallRequest(
199-
new DotnetInstallRoot(testEnv.InstallPath, InstallerUtilities.GetDefaultInstallArchitecture()),
200-
new UpdateChannel(firstChannel),
201-
InstallComponent.SDK,
202-
new InstallRequestOptions()));
203-
204-
ReleaseVersion? secondResolved = resolver.Resolve(
205-
new DotnetInstallRequest(
206-
new DotnetInstallRoot(testEnv.InstallPath, InstallerUtilities.GetDefaultInstallArchitecture()),
207-
new UpdateChannel(secondChannel),
208-
InstallComponent.SDK,
209-
new InstallRequestOptions()));
210-
211-
firstResolved.Should().NotBeNull($"Channel {firstChannel} should resolve to a version");
212-
secondResolved.Should().NotBeNull($"Channel {secondChannel} should resolve to a version");
213-
214-
if (expectDistinct && string.Equals(firstResolved!.ToString(), secondResolved!.ToString(), StringComparison.OrdinalIgnoreCase))
215-
{
216-
Console.WriteLine($"Skipping concurrent distinct-version scenario because both channels resolved to {firstResolved}");
217-
return;
218-
}
219-
var args1 = DnupTestUtilities.BuildArguments(firstChannel, testEnv.InstallPath, testEnv.ManifestPath);
220-
var args2 = DnupTestUtilities.BuildArguments(secondChannel, testEnv.InstallPath, testEnv.ManifestPath);
221-
222-
var installTask1 = Task.Run(() => DnupTestUtilities.RunDnupProcess(args1, captureOutput: true, workingDirectory: testEnv.TempRoot));
223-
var installTask2 = Task.Run(() => DnupTestUtilities.RunDnupProcess(args2, captureOutput: true, workingDirectory: testEnv.TempRoot));
224-
225-
DnupProcessResult[] results = await Task.WhenAll(installTask1, installTask2);
180+
var installTask1 = Task.Run(() => DnupTestUtilities.RunDnupProcess(args1, captureOutput: true, workingDirectory: testEnv.TempRoot));
181+
var installTask2 = Task.Run(() => DnupTestUtilities.RunDnupProcess(args2, captureOutput: true, workingDirectory: testEnv.TempRoot));
226182

227-
results[0].ExitCode.Should().Be(0,
228-
$"First concurrent install failed with exit code {results[0].ExitCode}. Output:\n{DnupTestUtilities.FormatOutputForAssertions(results[0])}");
229-
results[1].ExitCode.Should().Be(0,
230-
$"Second concurrent install failed with exit code {results[1].ExitCode}. Output:\n{DnupTestUtilities.FormatOutputForAssertions(results[1])}");
183+
var results = await Task.WhenAll(installTask1, installTask2);
231184

232-
var installs = new List<DotnetInstall>();
185+
results[0].exitCode.Should().Be(0,
186+
$"First concurrent install failed with exit code {results[0].exitCode}. Output:\n{results[0].output}");
187+
results[1].exitCode.Should().Be(0,
188+
$"Second concurrent install failed with exit code {results[1].exitCode}. Output:\n{results[1].output}");
233189

234-
using (var finalizeLock = new ScopedMutex(Constants.MutexNames.ModifyInstallationStates))
190+
string manifestMutexName = DnupTestUtilities.GetManifestMutexName(testEnv.ManifestPath);
191+
using (var finalizeLock = new ScopedMutex(manifestMutexName))
235192
{
236193
var manifest = new DnupSharedManifest(testEnv.ManifestPath);
237-
installs = manifest.GetInstalledVersions()
238-
.Where(i => DnupUtilities.PathsEqual(i.InstallRoot.Path, testEnv.InstallPath))
239-
.ToList();
240-
}
241-
242-
int expectedInstallCount = string.Equals(firstResolved!.ToString(), secondResolved!.ToString(), StringComparison.OrdinalIgnoreCase) ? 1 : 2;
243-
installs.Should().HaveCount(expectedInstallCount);
244-
245-
var expectedVersions = new HashSet<string>(StringComparer.OrdinalIgnoreCase)
246-
{
247-
firstResolved.ToString()!,
248-
secondResolved!.ToString()!
249-
};
250-
251-
foreach (var install in installs)
252-
{
253-
install.Component.Should().Be(InstallComponent.SDK);
254-
expectedVersions.Should().Contain(install.Version.ToString());
255-
DnupTestUtilities.ValidateInstall(install).Should().BeTrue(
256-
$"ArchiveInstallationValidator failed for concurrent install {install.Version} at {testEnv.InstallPath}");
194+
var installs = manifest.GetInstalledVersions().Where(i => DnupUtilities.PathsEqual(i.InstallRoot.Path, testEnv.InstallPath)).ToList();
195+
installs.Should().ContainSingle();
196+
installs[0].Version.ToString().Should().Be(channel);
257197
}
258-
259-
var actualVersions = installs.Select(i => i.Version.ToString()).ToHashSet(StringComparer.OrdinalIgnoreCase);
260-
actualVersions.Should().BeEquivalentTo(expectedVersions);
261198
}
262199
}

0 commit comments

Comments
 (0)