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/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/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..c5be40f 100644 --- a/src/dnvm/RestoreCommand.cs +++ b/src/dnvm/RestoreCommand.cs @@ -1,17 +1,15 @@ 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.Json; -using Spectre.Console; using StaticCs; using Zio; +using static Dnvm.InstallCommand; using RollForwardOptions = Dnvm.GlobalJsonSubset.SdkSubset.RollForwardOptions; namespace Dnvm; @@ -126,9 +124,30 @@ public enum Error NoVersion = 4, CouldntFetchReleaseIndex = 5, CantFindRequestedVersion = 6, + ManifestFileCorrupted = 7, + ManifestIOError = 8, } - 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 +215,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 +239,65 @@ 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) + 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) { - 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 + { + Manifest manifest; + try + { + manifest = await ManifestUtils.ReadOrCreateManifest(env); + } + catch (InvalidDataException) + { + logger.Error("Manifest file corrupted"); + return Error.ManifestFileCorrupted; + } + catch (Exception e) when (e is not OperationCanceledException) + { + logger.Error("Error reading manifest file: " + e.Message); + return Error.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) + { + return Error.IoError; + } } - return component.Version; + return release.Sdk.Version; } /// @@ -334,25 +388,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 +415,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 +442,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..1cdf8e0 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,27 @@ public async Task ExactVersionMatch() => await TestUtils.RunWithServer(async (se """); Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +100,27 @@ public async Task ExactMatchDirAbove() => await TestUtils.RunWithServer(UPath.Ro """); Assert.False(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(UPath.Root / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +134,28 @@ public async Task NewerPatchVersion() => await TestUtils.RunWithServer(async (se Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 43), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 43); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +170,27 @@ public async Task LatestPatch() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 43), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 43); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +206,27 @@ public async Task LatestFeature() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 100), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 100); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +243,27 @@ public async Task LatestMinor() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 43, 0), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 43, 0); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +281,27 @@ public async Task LatestMajor() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +320,28 @@ public async Task LatestMajorNoPrerelease() => await TestUtils.RunWithServer(asy Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(SemVersion.Parse("43.0.0", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = SemVersion.Parse("43.0.0", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +356,27 @@ public async Task PatchAndExact() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +392,27 @@ public async Task FeatureAndExact() => await TestUtils.RunWithServer(async (serv Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +429,27 @@ public async Task MinorAndExact() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +467,27 @@ public async Task MajorAndExact() => await TestUtils.RunWithServer(async (server Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(SemVersion.Parse("42.42.42", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = SemVersion.Parse("42.42.42", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +503,27 @@ public async Task PatchNoExact() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 43), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 43); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +539,27 @@ public async Task FeatureNoExact() => await TestUtils.RunWithServer(async (serve Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 42, 100), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 42, 100); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +576,27 @@ public async Task MinorNoExact() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(new SemVersion(42, 43, 0), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = new SemVersion(42, 43, 0); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +614,27 @@ public async Task MajorNoExact() => await TestUtils.RunWithServer(async (server, Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = SemVersion.Parse("99.99.99-preview", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +653,27 @@ public async Task MajorNoExactNoPrerelease() => await TestUtils.RunWithServer(as Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - var restoreResult = await RestoreCommand.Run(env, logger); - Assert.Equal(SemVersion.Parse("43.0.0", SemVersionStyles.Strict), restoreResult); - Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + var restoreResult = await RestoreCommand.Run(env, logger, new DnvmSubCommand.RestoreArgs() { Local = isLocalInstall }); + var expectedVersion = SemVersion.Parse("43.0.0", SemVersionStyles.Strict); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + + var manifest = await env.ReadManifest(); + var expectedManifest = Manifest.Empty.AddSdk(expectedVersion); + Assert.Equal(expectedManifest, manifest); + } }); - [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 +698,20 @@ 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 }); + var expectedVersion = new SemVersion(42, 42, 42); + Assert.Equal(expectedVersion, restoreResult); + if (isLocalInstall) + { + Assert.True(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); + } + else + { + Assert.False(env.CwdFs.DirectoryExists(env.Cwd / ".dotnet")); - Assert.Equal(new SemVersion(42, 42, 42), restoreResult); - Assert.True(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