From 32d822ad97210b11778536745be385e85911f092 Mon Sep 17 00:00:00 2001 From: Andy Gocke Date: Fri, 7 Jul 2023 14:01:37 -0700 Subject: [PATCH] Verify bad argument to select produces error (#85) Also produce a list of valid settings, and lower case the channel names. --- src/dnvm/ListCommand.cs | 2 +- src/dnvm/Manifest.cs | 2 ++ src/dnvm/Program.cs | 2 +- src/dnvm/SelectCommand.cs | 43 +++++++++++++++++++++++++++++++---- src/dnvm/Utilities/Result.cs | 21 +++++++++++++++++ test/Shared/MockServer.cs | 15 ++++++++++-- test/Shared/TaskScope.cs | 2 -- test/UnitTests/ListTests.cs | 6 ++--- test/UnitTests/SelectTests.cs | 30 ++++++++++++++++++++++-- 9 files changed, 107 insertions(+), 16 deletions(-) create mode 100644 src/dnvm/Utilities/Result.cs diff --git a/src/dnvm/ListCommand.cs b/src/dnvm/ListCommand.cs index 3085201..305bb87 100644 --- a/src/dnvm/ListCommand.cs +++ b/src/dnvm/ListCommand.cs @@ -41,7 +41,7 @@ public static void PrintSdks(Logger logger, Manifest manifest) string selected = manifest.CurrentSdkDir == channel.SdkDirName ? "*" : " "; foreach (var version in channel.InstalledSdkVersions) { - table.AddRow(selected, channel.ChannelName.ToString(), version, channel.SdkDirName.Name); + table.AddRow(selected, channel.ChannelName.GetLowerName(), version, channel.SdkDirName.Name); } } logger.Console.Write(table); diff --git a/src/dnvm/Manifest.cs b/src/dnvm/Manifest.cs index 408e436..5fad6d4 100644 --- a/src/dnvm/Manifest.cs +++ b/src/dnvm/Manifest.cs @@ -43,6 +43,8 @@ public static class Channels Channel.Preview => "The latest preview version", _ => throw new NotImplementedException(), }; + + public static string GetLowerName(this Channel c) => c.ToString().ToLowerInvariant(); } [GenerateSerde] diff --git a/src/dnvm/Program.cs b/src/dnvm/Program.cs index b566170..c0bc082 100644 --- a/src/dnvm/Program.cs +++ b/src/dnvm/Program.cs @@ -29,7 +29,7 @@ static async Task Main(string[] args) CommandArguments.InstallArguments o => (int)await InstallCommand.Run(globalOptions, logger, o), CommandArguments.UpdateArguments o => (int)await UpdateCommand.Run(globalOptions, logger, o), CommandArguments.ListArguments => (int)await ListCommand.Run(logger, dnvmFs), - CommandArguments.SelectArguments o => await SelectCommand.Run(globalOptions, logger, o), + CommandArguments.SelectArguments o => (int)await SelectCommand.Run(globalOptions, logger, o), CommandArguments.SelfInstallArguments o => (int)await SelfInstallCommand.Run(dnvmFs, globalOptions, logger, o), _ => throw ExceptionUtilities.Unreachable }; diff --git a/src/dnvm/SelectCommand.cs b/src/dnvm/SelectCommand.cs index c3a4d9f..e9d5896 100644 --- a/src/dnvm/SelectCommand.cs +++ b/src/dnvm/SelectCommand.cs @@ -1,27 +1,60 @@ using System.IO; +using System.Linq; using System.Net; using System.Threading.Tasks; using Serde.Json; +using Spectre.Console; namespace Dnvm; public static class SelectCommand { - public static async Task Run(GlobalOptions options, Logger logger, CommandArguments.SelectArguments args) + public enum Result + { + Success, + BadDirName, + } + + public static async Task Run(GlobalOptions options, Logger logger, CommandArguments.SelectArguments args) { var newDir = new SdkDirName(args.SdkDirName); var manifest = ManifestUtils.ReadOrCreateManifest(options.ManifestPath); - manifest = await SelectNewDir(options.DnvmHome, newDir, manifest); - File.WriteAllText(options.ManifestPath, JsonSerializer.Serialize(manifest)); - return 0; + switch (await RunWithManifest(options.DnvmHome, newDir, manifest, logger)) + { + case Result.Ok(var newManifest): + File.WriteAllText(options.ManifestPath, JsonSerializer.Serialize(newManifest)); + return Result.Success; + case Result.Err(var error): + return error; + default: + throw ExceptionUtilities.Unreachable; + }; + } + + public static async ValueTask> RunWithManifest(string dnvmHome, SdkDirName newDir, Manifest manifest, Logger logger) + { + var validDirs = manifest.TrackedChannels.Select(c => c.SdkDirName).ToList(); + + if (!validDirs.Contains(newDir)) + { + logger.Log($"Invalid SDK directory name: {newDir.Name}"); + logger.Log("Valid SDK directory names:"); + foreach (var dir in validDirs) + { + logger.Log($" {dir.Name}"); + } + return Result.BadDirName; + } + + return await SelectNewDir(dnvmHome, newDir, manifest); } /// /// Replaces the dotnet symlink with one pointing to the new SDK and /// updates the manifest to reflect the new SDK dir. /// - public static Task SelectNewDir(string dnvmHome, SdkDirName newDir, Manifest manifest) + private static Task SelectNewDir(string dnvmHome, SdkDirName newDir, Manifest manifest) { InstallCommand.RetargetSymlink(dnvmHome, newDir); return Task.FromResult(manifest with { CurrentSdkDir = newDir }); diff --git a/src/dnvm/Utilities/Result.cs b/src/dnvm/Utilities/Result.cs new file mode 100644 index 0000000..eb5b72d --- /dev/null +++ b/src/dnvm/Utilities/Result.cs @@ -0,0 +1,21 @@ + +using System; + +namespace Dnvm; + +public abstract record Result +{ + private Result() { } + + public sealed record Ok(TOk Value) : Result; + public sealed record Err(TErr Value) : Result; + + public static implicit operator Result(TOk success) => new Ok(success); + public static implicit operator Result(TErr error) => new Err(error); + + public TOk Unwrap() => this switch + { + Ok(var ok) => ok, + _ => throw new InvalidOperationException(), + }; +} \ No newline at end of file diff --git a/test/Shared/MockServer.cs b/test/Shared/MockServer.cs index 95d8222..d9f9509 100644 --- a/test/Shared/MockServer.cs +++ b/test/Shared/MockServer.cs @@ -39,6 +39,13 @@ public sealed class MockServer : IAsyncDisposable public DnvmReleases DnvmReleases { get; set; } + public static async Task WithScope(Func test) + => await TaskScope.With(async taskScope => + { + await using var mockServer = new MockServer(taskScope); + await test(mockServer); + }); + public MockServer(TaskScope scope) { _scope = scope; @@ -162,8 +169,12 @@ private void GetReleasesJson(HttpListenerResponse response) public ValueTask DisposeAsync() { - _listener.Stop(); - _listener.Close(); + try + { + _listener.Stop(); + _listener.Close(); + } + catch (HttpListenerException) { } return ValueTask.CompletedTask; } } \ No newline at end of file diff --git a/test/Shared/TaskScope.cs b/test/Shared/TaskScope.cs index 4cc6026..4cfb743 100644 --- a/test/Shared/TaskScope.cs +++ b/test/Shared/TaskScope.cs @@ -1,6 +1,4 @@ -using System.Diagnostics; - public sealed class TaskScope { private readonly CancellationTokenSource _cts = new(); diff --git a/test/UnitTests/ListTests.cs b/test/UnitTests/ListTests.cs index 225c92c..b6a749c 100644 --- a/test/UnitTests/ListTests.cs +++ b/test/UnitTests/ListTests.cs @@ -30,8 +30,8 @@ public void BasicList() ┌───┬─────────┬────────────────┬──────────┐ │ │ Channel │ Version │ Location │ ├───┼─────────┼────────────────┼──────────┤ -│ * │ Latest │ 1.0.0 │ dn │ -│ │ Preview │ 4.0.0-preview1 │ preview │ +│ * │ latest │ 1.0.0 │ dn │ +│ │ preview │ 4.0.0-preview1 │ preview │ └───┴─────────┴────────────────┴──────────┘ """; @@ -55,7 +55,7 @@ public async Task ListFromFile() ┌───┬─────────┬──────────┬──────────┐ │ │ Channel │ Version │ Location │ ├───┼─────────┼──────────┼──────────┤ -│ * │ Latest │ 42.42.42 │ dn │ +│ * │ latest │ 42.42.42 │ dn │ └───┴─────────┴──────────┴──────────┘ """; diff --git a/test/UnitTests/SelectTests.cs b/test/UnitTests/SelectTests.cs index 56f4bec..415bca6 100644 --- a/test/UnitTests/SelectTests.cs +++ b/test/UnitTests/SelectTests.cs @@ -1,4 +1,5 @@ +using System.Collections.Immutable; using System.Runtime.CompilerServices; using Dnvm.Test; using Spectre.Console.Testing; @@ -9,6 +10,7 @@ namespace Dnvm; public sealed class SelectTests : IDisposable { + private readonly TestConsole _console = new(); private readonly Logger _logger; private readonly TempDirectory _userHome = TestUtils.CreateTempDirectory(); private readonly TempDirectory _dnvmHome = TestUtils.CreateTempDirectory(); @@ -18,7 +20,7 @@ public sealed class SelectTests : IDisposable public SelectTests(ITestOutputHelper output) { var wrapper = new OutputWrapper(output); - _logger = new Logger(new TestConsole()); + _logger = new Logger(_console); _globalOptions = new GlobalOptions { DnvmHome = _dnvmHome.Path, UserHome = _userHome.Path, @@ -66,13 +68,37 @@ await TaskScope.With(async scope => Assert.Equal(GlobalOptions.DefaultSdkDirName, manifest.CurrentSdkDir); var previewSdkDir = new SdkDirName("preview"); - manifest = await SelectCommand.SelectNewDir(_globalOptions.DnvmHome, previewSdkDir, manifest); + manifest = (await SelectCommand.RunWithManifest(_globalOptions.DnvmHome, previewSdkDir, manifest, _logger)).Unwrap(); Assert.Equal(previewSdkDir, manifest.CurrentSdkDir); AssertSymlinkTarget(dotnetSymlink, previewSdkDir); }); } + [Fact] + public Task BadDirName() => MockServer.WithScope(async server => + { + var dn = new SdkDirName("dn"); + var manifest = new Manifest() + { + CurrentSdkDir = dn, + TrackedChannels = ImmutableArray.Create(new TrackedChannel + { + ChannelName = Channel.Latest, + SdkDirName = dn + }) + }; + var result = await SelectCommand.RunWithManifest(_globalOptions.DnvmHome, new SdkDirName("bad"), manifest, _logger); + Assert.Equal(SelectCommand.Result.BadDirName, result); + + Assert.Equal(""" +Invalid SDK directory name: bad +Valid SDK directory names: + dn + +""".Replace(Environment.NewLine, "\n"), _console.Output); + }); + private static void AssertSymlinkTarget(string dotnetSymlink, SdkDirName dirName) { if (OperatingSystem.IsWindows())