From c532a88561462a63985882ad226671ff6399ee21 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 11 Apr 2025 12:09:17 -0700 Subject: [PATCH 1/5] Add --local flag to restore and default to install to manifest dir --- src/dnvm/CommandLineArguments.cs | 10 +- src/dnvm/Program.cs | 2 +- src/dnvm/RestoreCommand.cs | 128 +++++++---- test/UnitTests/CommandLineTests.cs | 9 +- test/UnitTests/RestoreTests.cs | 331 ++++++++++++++++++++++------- 5 files changed, 355 insertions(+), 125 deletions(-) diff --git a/src/dnvm/CommandLineArguments.cs b/src/dnvm/CommandLineArguments.cs index 8bdf591..346f4e8 100644 --- a/src/dnvm/CommandLineArguments.cs +++ b/src/dnvm/CommandLineArguments.cs @@ -174,9 +174,17 @@ public sealed partial record PruneArgs : DnvmSubCommand } [Command("restore", Summary = "Restore the SDK listed in the global.json file.", - Description = "Downloads the SDK in the global.json in or above the current directory to the .dotnet folder in the same directory.")] + Description = "Downloads the SDK in the global.json in or above the current directory.")] public sealed partial record RestoreArgs : DnvmSubCommand { + [CommandOption("-l|--local", Description = "Install the sdk into the .dotnet folder in the same directory as global.json.")] + public bool? Local { get; init; } = null; + + [CommandOption("-f|--force", Description = "Force install the SDK, even if already installed.")] + public bool? Force { get; init; } = null; + + [CommandOption("-v|--verbose", Description = "Print extra debugging info to the console.")] + public bool? Verbose { get; init; } = null; } /// diff --git a/src/dnvm/Program.cs b/src/dnvm/Program.cs index 1044df3..dbf07f3 100644 --- a/src/dnvm/Program.cs +++ b/src/dnvm/Program.cs @@ -74,7 +74,7 @@ internal static async Task Dnvm(DnvmEnv env, Logger logger, DnvmArgs args) DnvmSubCommand.UntrackArgs a => await UntrackCommand.Run(env, logger, a.Channel), DnvmSubCommand.UninstallArgs a => await UninstallCommand.Run(env, logger, a.SdkVersion, a.SdkDir), DnvmSubCommand.PruneArgs a => await PruneCommand.Run(env, logger, a), - DnvmSubCommand.RestoreArgs => await RestoreCommand.Run(env, logger) switch { + DnvmSubCommand.RestoreArgs a => await RestoreCommand.Run(env, logger, a) switch { Result.Ok => 0, Result.Err x => (int)x.Value, }, diff --git a/src/dnvm/RestoreCommand.cs b/src/dnvm/RestoreCommand.cs index eb7669f..26ac62e 100644 --- a/src/dnvm/RestoreCommand.cs +++ b/src/dnvm/RestoreCommand.cs @@ -8,10 +8,12 @@ using System.Threading.Tasks; using Semver; using Serde; +using Serde.CmdLine; using Serde.Json; using Spectre.Console; using StaticCs; using Zio; +using static Dnvm.InstallCommand; using RollForwardOptions = Dnvm.GlobalJsonSubset.SdkSubset.RollForwardOptions; namespace Dnvm; @@ -128,7 +130,26 @@ public enum Error CantFindRequestedVersion = 6, } - public static async Task> Run(DnvmEnv env, Logger logger) + public sealed record Options + { + public bool Local { get; init; } + + public bool Force { get; init; } + + public bool Verbose { get; init; } + } + + public static Task> Run(DnvmEnv env, Logger logger, DnvmSubCommand.RestoreArgs args) + { + return Run(env, logger, new Options + { + Local = args.Local ?? false, + Force = args.Force ?? false, + Verbose = args.Verbose ?? false, + }); + } + + public static async Task> Run(DnvmEnv env, Logger logger, Options options) { UPath? globalJsonPathOpt = null; UPath cwd = env.Cwd; @@ -196,18 +217,18 @@ public static async Task> Run(DnvmEnv env, Logger logg var installDir = globalJsonPath.GetDirectory() / ".dotnet"; - var sdks = await GetSortedSdks(env.HttpClient, versionIndex, allowPrerelease, sdk.Version.ToMajorMinor()); + var releases = await GetSortedReleases(env.HttpClient, versionIndex, allowPrerelease, sdk.Version.ToMajorMinor()); - ChannelReleaseIndex.Component? Search(Comparison comparer, bool preferExact) + ChannelReleaseIndex.Release? Search(Comparison comparer, bool preferExact) { - if (preferExact && BinarySearchLatest(sdks, version, SemVersion.CompareSortOrder) is { } exactMatch) + if (preferExact && BinarySearchLatest(releases, version, SemVersion.CompareSortOrder) is { } exactMatch) { return exactMatch; } - return BinarySearchLatest(sdks, version, comparer); + return BinarySearchLatest(releases, version, comparer); } - ChannelReleaseIndex.Component? component = rollForward switch + ChannelReleaseIndex.Release? release = rollForward switch { RollForwardOptions.Patch => Search(LatestPatchComparison, preferExact: true), RollForwardOptions.Feature => Search(LatestFeatureComparison, preferExact: true), @@ -220,30 +241,59 @@ public static async Task> Run(DnvmEnv env, Logger logg RollForwardOptions.Disable => Search(SemVersion.CompareSortOrder, preferExact: false), }; - if (component is null) + if (release is null) { logger.Error("No SDK found that matches the requested version."); return Error.CantFindRequestedVersion; } - var downloadUrl = component.Files.Single(f => f.Rid == Utilities.CurrentRID.ToString() && f.Url.EndsWith(Utilities.ZipSuffix)).Url; - - var error = await InstallCommand.InstallSdkToDir( - curMuxerVersion: null, // we shouldn't have a muxer yet, and we can overwrite it if we do - component.Version, - env.HttpClient, - downloadUrl, - env.CwdFs, - installDir, - env.TempFs, - logger - ); - if (error is not null) + var downloadUrl = release.Sdk.Files.Single(f => f.Rid == Utilities.CurrentRID.ToString() && f.Url.EndsWith(Utilities.ZipSuffix)).Url; + + if (options.Local) { - return Error.IoError; + var error = await InstallCommand.InstallSdkToDir( + curMuxerVersion: null, // we shouldn't have a muxer yet, and we can overwrite it if we do + release.Sdk.Version, + env.HttpClient, + downloadUrl, + env.CwdFs, + installDir, + env.TempFs, + logger + ); + if (error is not null) + { + return Error.IoError; + } + } + else + { + // TODO: Refactor this duplication + Manifest manifest; + try + { + manifest = await ManifestUtils.ReadOrCreateManifest(env); + } + catch (InvalidDataException) + { + logger.Error("Manifest file corrupted"); + return Error.IoError; //Result.ManifestFileCorrupted; + } + catch (Exception e) when (e is not OperationCanceledException) + { + logger.Error("Error reading manifest file: " + e.Message); + return Error.IoError; // Result.ManifestIOError; + } + + + var error = await InstallCommand.InstallSdk(env, manifest, release.Sdk, release, manifest.CurrentSdkDir, logger); + if (error is not Result.Ok) + { + return Error.IoError; + } } - return component.Version; + return release.Sdk.Version; } /// @@ -334,25 +384,25 @@ private static int LatestMajorComparison(SemVersion a, SemVersion b) } /// - /// Finds the component with the given version. If multiple components have the same version, - /// the one with the newest version is returned. + /// Finds the release with the given SDK version. If multiple releases have the same SDK version, + /// the one with the newest SDK version is returned. /// - /// A list of the components in descending sort order. - /// Version of the component to find. + /// A list of the releases in descending sort order. + /// Version of the SDK to find. /// Custom comparison for versions. - /// The component with the newest matching version, or null if no matching version is found. - private static ChannelReleaseIndex.Component? BinarySearchLatest( - List sdks, + /// The SDK with the newest matching version, or null if no matching version is found. + private static ChannelReleaseIndex.Release? BinarySearchLatest( + List releases, SemVersion version, Comparison comparer) { // Note: the list is sorted in descending order. int left = 0; - int right = sdks.Count; + int right = releases.Count; while (left < right) { int mid = left + (right - left) / 2; - if (comparer(sdks[mid].Version, version) > 0) + if (comparer(releases[mid].Sdk.Version, version) > 0) { left = mid + 1; } @@ -361,20 +411,20 @@ private static int LatestMajorComparison(SemVersion a, SemVersion b) right = mid; } } - return left < sdks.Count && comparer(sdks[left].Version, version) == 0 ? sdks[left] : null; + return left < releases.Count && comparer(releases[left].Sdk.Version, version) == 0 ? releases[left] : null; } /// - /// Returns a sorted (descending) list of SDks. If is not - /// null, only SDKs for that major+minor version are returned. + /// Returns list of releases sorted (descending) by SDK version. If is not + /// null, only releases for that major+minor version are returned. /// - private static async Task> GetSortedSdks( + private static async Task> GetSortedReleases( ScopedHttpClient httpClient, DotnetReleasesIndex versionIndex, bool allowPrerelease, string minMajorMinor) { - var sdks = new List(); + var releases = new List(); foreach (var releaseIndex in versionIndex.ChannelIndices) { if (minMajorMinor is not null && double.Parse(minMajorMinor) > double.Parse(releaseIndex.MajorMinorVersion)) @@ -388,13 +438,13 @@ private static int LatestMajorComparison(SemVersion a, SemVersion b) { if (allowPrerelease || !sdk.Version.IsPrerelease) { - sdks.Add(sdk); + releases.Add(release); } } } } // Sort in descending order. - sdks.Sort((a, b) => b.Version.CompareSortOrderTo(a.Version)); - return sdks; + releases.Sort((a, b) => b.Sdk.Version.CompareSortOrderTo(a.Sdk.Version)); + return releases; } } \ No newline at end of file diff --git a/test/UnitTests/CommandLineTests.cs b/test/UnitTests/CommandLineTests.cs index e511885..f72ecd1 100644 --- a/test/UnitTests/CommandLineTests.cs +++ b/test/UnitTests/CommandLineTests.cs @@ -448,12 +448,15 @@ public void RestoreHelp(string param) console, [ "restore", param ])); Assert.Equal(""" -usage: dnvm restore [-h | --help] +usage: dnvm restore [-l | --local] [-f | --force] [-v | --verbose] [-h | --help] -Downloads the SDK in the global.json in or above the current directory to the -.dotnet folder in the same directory. +Downloads the SDK in the global.json in or above the current directory. Options: + -l, --local Install the sdk into the .dotnet folder in the same directory +as global.json. + -f, --force Force install the SDK, even if already installed. + -v, --verbose Print extra debugging info to the console. -h, --help Show help information. diff --git a/test/UnitTests/RestoreTests.cs b/test/UnitTests/RestoreTests.cs index 872812d..13ec805 100644 --- a/test/UnitTests/RestoreTests.cs +++ b/test/UnitTests/RestoreTests.cs @@ -8,12 +8,14 @@ namespace Dnvm.Test; public sealed class RestoreTests { - [Fact] - public async Task NoGlobalJson() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task NoGlobalJson(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var console = new TestConsole(); var logger = new Logger(console); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(RestoreCommand.Error.NoGlobalJson, restoreResult); Assert.Equal(""" Error: No global.json found in the current directory or any of its parents. @@ -21,19 +23,23 @@ public async Task NoGlobalJson() => await TestUtils.RunWithServer(async (server, """.NormalizeLineEndings(), console.Output.TrimLines()); }); - [Fact] - public async Task GlobalJsonNoSdkSection() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task GlobalJsonNoSdkSection(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ {} """); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(RestoreCommand.Error.NoSdkSection, restoreResult); }); - [Fact] - public async Task GlobalJsonNoVersion() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task GlobalJsonNoVersion(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -41,12 +47,14 @@ public async Task GlobalJsonNoVersion() => await TestUtils.RunWithServer(async ( "sdk": {} } """); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(RestoreCommand.Error.NoVersion, restoreResult); }); - [Fact] - public async Task ExactVersionMatch() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ExactVersionMatch(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", $$""" @@ -58,13 +66,22 @@ public async Task ExactVersionMatch() => await TestUtils.RunWithServer(async (se """); Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task ExactMatchDirAbove() => await TestUtils.RunWithServer(UPath.Root / "a" / "b" / "c", + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ExactMatchDirAbove(bool isLocalInstall) => await TestUtils.RunWithServer(UPath.Root / "a" / "b" / "c", async (server, env) => { var logger = new Logger(new TestConsole()); @@ -78,13 +95,22 @@ public async Task ExactMatchDirAbove() => await TestUtils.RunWithServer(UPath.Ro """); Assert.False(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + } }); - [Fact] - public async Task NewerPatchVersion() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task NewerPatchVersion(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -98,13 +124,22 @@ public async Task NewerPatchVersion() => await TestUtils.RunWithServer(async (se Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 43), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task LatestPatch() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task LatestPatch(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -119,13 +154,22 @@ public async Task LatestPatch() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 43), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task LatestFeature() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task LatestFeature(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -141,13 +185,22 @@ public async Task LatestFeature() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 100), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task LatestMinor() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task LatestMinor(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -164,13 +217,22 @@ public async Task LatestMinor() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 43, 0), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task LatestMajor() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task LatestMajor(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -188,13 +250,22 @@ public async Task LatestMajor() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task LatestMajorNoPrerelease() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task LatestMajorNoPrerelease(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -213,13 +284,23 @@ public async Task LatestMajorNoPrerelease() => await TestUtils.RunWithServer(asy Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(SemVersion.Parse("43.0.0", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task PatchAndExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PatchAndExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -234,13 +315,22 @@ public async Task PatchAndExact() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task FeatureAndExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task FeatureAndExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -256,13 +346,22 @@ public async Task FeatureAndExact() => await TestUtils.RunWithServer(async (serv Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task MinorAndExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task MinorAndExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -279,13 +378,22 @@ public async Task MinorAndExact() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task MajorAndExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task MajorAndExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -303,13 +411,22 @@ public async Task MajorAndExact() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(SemVersion.Parse("42.42.42", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task PatchNoExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task PatchNoExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -325,13 +442,22 @@ public async Task PatchNoExact() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 43), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task FeatureNoExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task FeatureNoExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -347,13 +473,22 @@ public async Task FeatureNoExact() => await TestUtils.RunWithServer(async (serve Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 100), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task MinorNoExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task MinorNoExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -370,13 +505,22 @@ public async Task MinorNoExact() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 43, 0), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task MajorNoExact() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task MajorNoExact(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -394,13 +538,22 @@ public async Task MajorNoExact() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task MajorNoExactNoPrerelease() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task MajorNoExactNoPrerelease(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", """ @@ -419,13 +572,22 @@ public async Task MajorNoExactNoPrerelease() => await TestUtils.RunWithServer(as Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(SemVersion.Parse("43.0.0", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); - [Fact] - public async Task ZipAndExeFiles() => await TestUtils.RunWithServer(async (server, env) => + [Theory] + [InlineData(true)] + [InlineData(false)] + public async Task ZipAndExeFiles(bool isLocalInstall) => await TestUtils.RunWithServer(async (server, env) => { var logger = new Logger(new TestConsole()); env.CwdFs.WriteAllText(env.Cwd / "global.json", $$""" @@ -450,9 +612,16 @@ public async Task ZipAndExeFiles() => await TestUtils.RunWithServer(async (serve Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } }); } \ No newline at end of file From 85f3e950002a0d94f0415c5cb9d456c61d7bd46b Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 11 Apr 2025 12:32:17 -0700 Subject: [PATCH 2/5] Don't restore if already installed --- src/dnvm/InstallCommand.cs | 4 ++-- src/dnvm/ManifestUtils.cs | 5 +++++ src/dnvm/RestoreCommand.cs | 6 ++++++ 3 files changed, 13 insertions(+), 2 deletions(-) diff --git a/src/dnvm/InstallCommand.cs b/src/dnvm/InstallCommand.cs index 3e296c8..cd5f72c 100644 --- a/src/dnvm/InstallCommand.cs +++ b/src/dnvm/InstallCommand.cs @@ -68,7 +68,7 @@ public static async Task Run(DnvmEnv env, Logger logger, Options options var sdkVersion = options.SdkVersion; var channel = new Channel.VersionedMajorMinor(sdkVersion.Major, sdkVersion.Minor); - if (!options.Force && manifest.InstalledSdks.Any(s => s.SdkVersion == sdkVersion && s.SdkDirName == sdkDir)) + if (!options.Force && ManifestUtils.IsSdkInstalled(manifest, sdkVersion, sdkDir)) { logger.Log($"Version {sdkVersion} is already installed in directory '{sdkDir.Name}'." + " Skipping installation. To install anyway, pass --force."); @@ -241,7 +241,7 @@ internal static async Task> InstallSdk( var result = JsonSerializer.Serialize(manifest); logger.Info("Existing manifest: " + result); - if (!manifest.InstalledSdks.Any(s => s.SdkVersion == sdkVersion && s.SdkDirName == sdkDir)) + if (!ManifestUtils.IsSdkInstalled(manifest, sdkVersion, sdkDir)) { manifest = manifest with { diff --git a/src/dnvm/ManifestUtils.cs b/src/dnvm/ManifestUtils.cs index e26c573..8940dc4 100644 --- a/src/dnvm/ManifestUtils.cs +++ b/src/dnvm/ManifestUtils.cs @@ -115,6 +115,11 @@ public static Manifest AddSdk(this Manifest manifest, }; } + public static bool IsSdkInstalled(Manifest manifest, SemVersion version, SdkDirName dirName) + { + return manifest.InstalledSdks.Any(s => s.SdkVersion == version && s.SdkDirName == dirName); + } + /// /// Either reads a manifest in the current format, or reads a /// manifest in the old format and converts it to the new format. diff --git a/src/dnvm/RestoreCommand.cs b/src/dnvm/RestoreCommand.cs index 26ac62e..4cd97fc 100644 --- a/src/dnvm/RestoreCommand.cs +++ b/src/dnvm/RestoreCommand.cs @@ -285,6 +285,12 @@ public static async Task> Run(DnvmEnv env, Logger logg return Error.IoError; // Result.ManifestIOError; } + if (!options.Force && ManifestUtils.IsSdkInstalled(manifest, release.Sdk.Version, manifest.CurrentSdkDir)) + { + logger.Log($"Version {release.Sdk.Version} is already installed in directory '{manifest.CurrentSdkDir.Name}'." + + " Skipping installation. To install anyway, pass --force."); + return release.Sdk.Version; + } var error = await InstallCommand.InstallSdk(env, manifest, release.Sdk, release, manifest.CurrentSdkDir, logger); if (error is not Result.Ok) From 7c6b9e976e84f6de489ac0d0092f08c2a005b8e7 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 11 Apr 2025 12:36:46 -0700 Subject: [PATCH 3/5] Clean up errors --- src/dnvm/RestoreCommand.cs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/dnvm/RestoreCommand.cs b/src/dnvm/RestoreCommand.cs index 4cd97fc..6a3b8fe 100644 --- a/src/dnvm/RestoreCommand.cs +++ b/src/dnvm/RestoreCommand.cs @@ -1,16 +1,12 @@ using System; using System.Collections.Generic; -using System.ComponentModel; using System.IO; using System.Linq; -using System.Net.Http; using System.Threading.Tasks; using Semver; using Serde; -using Serde.CmdLine; using Serde.Json; -using Spectre.Console; using StaticCs; using Zio; using static Dnvm.InstallCommand; @@ -128,6 +124,8 @@ public enum Error NoVersion = 4, CouldntFetchReleaseIndex = 5, CantFindRequestedVersion = 6, + ManifestFileCorrupted = 7, + ManifestIOError = 8, } public sealed record Options @@ -268,7 +266,6 @@ public static async Task> Run(DnvmEnv env, Logger logg } else { - // TODO: Refactor this duplication Manifest manifest; try { @@ -277,12 +274,12 @@ public static async Task> Run(DnvmEnv env, Logger logg catch (InvalidDataException) { logger.Error("Manifest file corrupted"); - return Error.IoError; //Result.ManifestFileCorrupted; + return Error.ManifestFileCorrupted; } catch (Exception e) when (e is not OperationCanceledException) { logger.Error("Error reading manifest file: " + e.Message); - return Error.IoError; // Result.ManifestIOError; + return Error.ManifestIOError; } if (!options.Force && ManifestUtils.IsSdkInstalled(manifest, release.Sdk.Version, manifest.CurrentSdkDir)) From 58d22f74a34e203d73d24786ac4603c9cb0e71c7 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 11 Apr 2025 13:10:16 -0700 Subject: [PATCH 4/5] Add more assertions to tests --- test/UnitTests/RestoreTests.cs | 128 ++++++++++++++++++++++++++++----- 1 file changed, 109 insertions(+), 19 deletions(-) diff --git a/test/UnitTests/RestoreTests.cs b/test/UnitTests/RestoreTests.cs index 13ec805..1cdf8e0 100644 --- a/test/UnitTests/RestoreTests.cs +++ b/test/UnitTests/RestoreTests.cs @@ -67,7 +67,8 @@ public async Task ExactVersionMatch(bool isLocalInstall) => await TestUtils.RunW Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -75,6 +76,10 @@ public async Task ExactVersionMatch(bool isLocalInstall) => await TestUtils.RunW else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -96,7 +101,8 @@ public async Task ExactMatchDirAbove(bool isLocalInstall) => await TestUtils.Run Assert.False(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); @@ -104,6 +110,10 @@ public async Task ExactMatchDirAbove(bool isLocalInstall) => await TestUtils.Run else { Assert.False(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -125,7 +135,8 @@ public async Task NewerPatchVersion(bool isLocalInstall) => await TestUtils.RunW Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 43), restoreResult); + var expectedVersion = new SemVersion(42, 42, 43); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -133,6 +144,11 @@ public async Task NewerPatchVersion(bool isLocalInstall) => await TestUtils.RunW else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -155,7 +171,8 @@ public async Task LatestPatch(bool isLocalInstall) => await TestUtils.RunWithSer Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 43), restoreResult); + var expectedVersion = new SemVersion(42, 42, 43); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -163,6 +180,10 @@ public async Task LatestPatch(bool isLocalInstall) => await TestUtils.RunWithSer else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -186,7 +207,8 @@ public async Task LatestFeature(bool isLocalInstall) => await TestUtils.RunWithS Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 100), restoreResult); + var expectedVersion = new SemVersion(42, 42, 100); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -194,6 +216,10 @@ public async Task LatestFeature(bool isLocalInstall) => await TestUtils.RunWithS else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -218,7 +244,8 @@ public async Task LatestMinor(bool isLocalInstall) => await TestUtils.RunWithSer Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 43, 0), restoreResult); + var expectedVersion = new SemVersion(42, 43, 0); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -226,6 +253,10 @@ public async Task LatestMinor(bool isLocalInstall) => await TestUtils.RunWithSer else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -251,7 +282,8 @@ public async Task LatestMajor(bool isLocalInstall) => await TestUtils.RunWithSer Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict), restoreResult); + var expectedVersion = SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -259,6 +291,10 @@ public async Task LatestMajor(bool isLocalInstall) => await TestUtils.RunWithSer else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -285,7 +321,8 @@ public async Task LatestMajorNoPrerelease(bool isLocalInstall) => await TestUtil Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(SemVersion.Parse("43.0.0", SemVersionStyles.Strict), restoreResult); + var expectedVersion = SemVersion.Parse("43.0.0", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { @@ -294,6 +331,10 @@ public async Task LatestMajorNoPrerelease(bool isLocalInstall) => await TestUtil else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -316,7 +357,8 @@ public async Task PatchAndExact(bool isLocalInstall) => await TestUtils.RunWithS Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -324,6 +366,10 @@ public async Task PatchAndExact(bool isLocalInstall) => await TestUtils.RunWithS else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -347,7 +393,8 @@ public async Task FeatureAndExact(bool isLocalInstall) => await TestUtils.RunWit Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -355,6 +402,10 @@ public async Task FeatureAndExact(bool isLocalInstall) => await TestUtils.RunWit else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -379,7 +430,8 @@ public async Task MinorAndExact(bool isLocalInstall) => await TestUtils.RunWithS Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -387,6 +439,10 @@ public async Task MinorAndExact(bool isLocalInstall) => await TestUtils.RunWithS else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -412,7 +468,8 @@ public async Task MajorAndExact(bool isLocalInstall) => await TestUtils.RunWithS Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(SemVersion.Parse("42.42.42", SemVersionStyles.Strict), restoreResult); + var expectedVersion = SemVersion.Parse("42.42.42", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -420,6 +477,10 @@ public async Task MajorAndExact(bool isLocalInstall) => await TestUtils.RunWithS else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -443,7 +504,8 @@ public async Task PatchNoExact(bool isLocalInstall) => await TestUtils.RunWithSe Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 43), restoreResult); + var expectedVersion = new SemVersion(42, 42, 43); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -451,6 +513,10 @@ public async Task PatchNoExact(bool isLocalInstall) => await TestUtils.RunWithSe else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -474,7 +540,8 @@ public async Task FeatureNoExact(bool isLocalInstall) => await TestUtils.RunWith Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 42, 100), restoreResult); + var expectedVersion = new SemVersion(42, 42, 100); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -482,6 +549,10 @@ public async Task FeatureNoExact(bool isLocalInstall) => await TestUtils.RunWith else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -506,7 +577,8 @@ public async Task MinorNoExact(bool isLocalInstall) => await TestUtils.RunWithSe Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(new SemVersion(42, 43, 0), restoreResult); + var expectedVersion = new SemVersion(42, 43, 0); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -514,6 +586,10 @@ public async Task MinorNoExact(bool isLocalInstall) => await TestUtils.RunWithSe else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -539,7 +615,8 @@ public async Task MajorNoExact(bool isLocalInstall) => await TestUtils.RunWithSe Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict), restoreResult); + var expectedVersion = SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -547,6 +624,10 @@ public async Task MajorNoExact(bool isLocalInstall) => await TestUtils.RunWithSe else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -573,7 +654,8 @@ public async Task MajorNoExactNoPrerelease(bool isLocalInstall) => await TestUti Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - Assert.Equal(SemVersion.Parse("43.0.0", SemVersionStyles.Strict), restoreResult); + var expectedVersion = SemVersion.Parse("43.0.0", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -581,6 +663,10 @@ public async Task MajorNoExactNoPrerelease(bool isLocalInstall) => await TestUti else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); @@ -613,8 +699,8 @@ public async Task ZipAndExeFiles(bool isLocalInstall) => await TestUtils.RunWith Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); - - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); if (isLocalInstall) { Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); @@ -622,6 +708,10 @@ public async Task ZipAndExeFiles(bool isLocalInstall) => await TestUtils.RunWith else { Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); } }); } \ No newline at end of file From 413871b593cc56bf2ddff5700f3193b007365dd6 Mon Sep 17 00:00:00 2001 From: Matt Kotsenas Date: Fri, 11 Apr 2025 13:24:02 -0700 Subject: [PATCH 5/5] Add log message for discovered and selected versions --- src/dnvm/RestoreCommand.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/dnvm/RestoreCommand.cs b/src/dnvm/RestoreCommand.cs index 6a3b8fe..c5be40f 100644 --- a/src/dnvm/RestoreCommand.cs +++ b/src/dnvm/RestoreCommand.cs @@ -245,6 +245,7 @@ public static async Task> Run(DnvmEnv env, Logger logg return Error.CantFindRequestedVersion; } + logger.Log($"Found version {sdk.Version} in global.json. Selected version {release.Sdk.Version} based on roll forward rules."); var downloadUrl = release.Sdk.Files.Single(f => f.Rid == Utilities.CurrentRID.ToString() && f.Url.EndsWith(Utilities.ZipSuffix)).Url; if (options.Local)