diff --git a/Directory.Packages.props b/Directory.Packages.props index 13ad629a4..3c8c5b165 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -1,14 +1,14 @@ true - 10.0.0-preview.6.25358.103 + 10.0.0 9.0.4 8.0.15 2.70.0 1.6.0 1.8.1 1.8.0-beta.1 - 10.0.0-preview.6.25358.103 + 10.0.0 8.0.0 @@ -65,7 +65,7 @@ - + @@ -76,8 +76,7 @@ - - + diff --git a/examples/Container/Backend/Dockerfile b/examples/Container/Backend/Dockerfile index d47d19c94..b00a0405d 100644 --- a/examples/Container/Backend/Dockerfile +++ b/examples/Container/Backend/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build-env +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env WORKDIR /app # Copy everything @@ -8,7 +8,7 @@ RUN dotnet restore examples/Container/Backend RUN dotnet publish examples/Container/Backend -c Release -o out # Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview +FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app COPY --from=build-env /app/out . ENTRYPOINT ["dotnet", "Backend.dll"] \ No newline at end of file diff --git a/examples/Container/Frontend/Dockerfile b/examples/Container/Frontend/Dockerfile index 4a50f8ec1..05386af10 100644 --- a/examples/Container/Frontend/Dockerfile +++ b/examples/Container/Frontend/Dockerfile @@ -1,4 +1,4 @@ -FROM mcr.microsoft.com/dotnet/sdk:10.0-preview AS build-env +FROM mcr.microsoft.com/dotnet/sdk:10.0 AS build-env WORKDIR /app # Copy everything @@ -8,7 +8,7 @@ RUN dotnet restore examples/Container/Frontend RUN dotnet publish examples/Container/Frontend -c Release -o out # Build runtime image -FROM mcr.microsoft.com/dotnet/aspnet:10.0-preview +FROM mcr.microsoft.com/dotnet/aspnet:10.0 WORKDIR /app COPY --from=build-env /app/out . ENTRYPOINT ["dotnet", "Frontend.dll"] \ No newline at end of file diff --git a/global.json b/global.json index efa5f91c5..512142d2b 100644 --- a/global.json +++ b/global.json @@ -1,6 +1,6 @@ { "sdk": { - "version": "10.0.100-preview.6.25358.103", + "version": "10.0.100", "rollForward": "latestFeature" } } diff --git a/perf/benchmarkapps/GrpcClient/Program.cs b/perf/benchmarkapps/GrpcClient/Program.cs index 2cd21e095..dd737563b 100644 --- a/perf/benchmarkapps/GrpcClient/Program.cs +++ b/perf/benchmarkapps/GrpcClient/Program.cs @@ -62,66 +62,66 @@ class Program public static async Task Main(string[] args) { - var urlOption = new Option(new string[] { "-u", "--url" }, "The server url to request") { IsRequired = true }; - var udsFileNameOption = new Option(new string[] { "--udsFileName" }, "The Unix Domain Socket file name"); - var namedPipeNameOption = new Option(new string[] { "--namedPipeName" }, "The Named Pipe name"); - var connectionsOption = new Option(new string[] { "-c", "--connections" }, () => 1, "Total number of connections to keep open"); - var warmupOption = new Option(new string[] { "-w", "--warmup" }, () => 5, "Duration of the warmup in seconds"); - var durationOption = new Option(new string[] { "-d", "--duration" }, () => 10, "Duration of the test in seconds"); - var callCountOption = new Option(new string[] { "--callCount" }, "Call count of test"); - var scenarioOption = new Option(new string[] { "-s", "--scenario" }, "Scenario to run") { IsRequired = true }; - var latencyOption = new Option(new string[] { "-l", "--latency" }, () => false, "Whether to collect detailed latency"); - var protocolOption = new Option(new string[] { "-p", "--protocol" }, "HTTP protocol") { IsRequired = true }; - var logOption = new Option(new string[] { "-log", "--logLevel" }, () => LogLevel.None, "The log level to use for Console logging"); - var requestSizeOption = new Option(new string[] { "--requestSize" }, "Request payload size"); - var responseSizeOption = new Option(new string[] { "--responseSize" }, "Response payload size"); - var grpcClientTypeOption = new Option(new string[] { "--grpcClientType" }, () => GrpcClientType.GrpcNetClient, "Whether to use Grpc.NetClient or Grpc.Core client"); - var streamsOption = new Option(new string[] { "--streams" }, () => 1, "Maximum concurrent streams per connection"); - var enableCertAuthOption = new Option(new string[] { "--enableCertAuth" }, () => false, "Flag indicating whether client sends a client certificate"); - var deadlineOption = new Option(new string[] { "--deadline" }, "Duration of deadline in seconds"); - var winHttpHandlerOption = new Option(new string[] { "--winhttphandler" }, () => false, "Whether to use WinHttpHandler with Grpc.Net.Client"); + var urlOption = new Option("--url", ["-u"]) { Description = "The server url to request", Required = true }; + var udsFileNameOption = new Option("--udsFileName") { Description = "The Unix Domain Socket file name" }; + var namedPipeNameOption = new Option("--namedPipeName") { Description = "The Named Pipe name" }; + var connectionsOption = new Option("--connections", ["-c"]) { DefaultValueFactory = (r) => 1, Description = "Total number of connections to keep open" }; + var warmupOption = new Option("--warmup", ["-w"]) { DefaultValueFactory = (r) => 5, Description = "Duration of the warmup in seconds" }; + var durationOption = new Option("--duration", ["-d"]) { DefaultValueFactory = (r) => 10, Description = "Duration of the test in seconds" }; + var callCountOption = new Option("--callCount") { Description = "Call count of test" }; + var scenarioOption = new Option("--scenario", ["-s"]) { Description = "Scenario to run", Required = true }; + var latencyOption = new Option("--latency", ["-l"]) { DefaultValueFactory = (r) => false, Description = "Whether to collect detailed latency" }; + var protocolOption = new Option("--protocol", ["-p"]) { Description = "HTTP protocol", Required = true }; + var logOption = new Option("--logLevel", ["-log"]) { DefaultValueFactory = (_) => LogLevel.None, Description = "The log level to use for Console logging" }; + var requestSizeOption = new Option("--requestSize") { Description = "Request payload size" }; + var responseSizeOption = new Option("--responseSize") { Description = "Response payload size" }; + var grpcClientTypeOption = new Option("--grpcClientType") { DefaultValueFactory = (r) => GrpcClientType.GrpcNetClient, Description = "Whether to use Grpc.NetClient or Grpc.Core client" }; + var streamsOption = new Option("--streams") { DefaultValueFactory = (r) => 1, Description = "Maximum concurrent streams per connection" }; + var enableCertAuthOption = new Option("--enableCertAuth") { DefaultValueFactory = (r) => false, Description = "Flag indicating whether client sends a client certificate" }; + var deadlineOption = new Option("--deadline") { Description = "Duration of deadline in seconds" }; + var winHttpHandlerOption = new Option("--winhttphandler") { DefaultValueFactory = (r) => false, Description = "Whether to use WinHttpHandler with Grpc.Net.Client" }; var rootCommand = new RootCommand(); - rootCommand.AddOption(urlOption); - rootCommand.AddOption(udsFileNameOption); - rootCommand.AddOption(namedPipeNameOption); - rootCommand.AddOption(connectionsOption); - rootCommand.AddOption(warmupOption); - rootCommand.AddOption(durationOption); - rootCommand.AddOption(callCountOption); - rootCommand.AddOption(scenarioOption); - rootCommand.AddOption(latencyOption); - rootCommand.AddOption(protocolOption); - rootCommand.AddOption(logOption); - rootCommand.AddOption(requestSizeOption); - rootCommand.AddOption(responseSizeOption); - rootCommand.AddOption(grpcClientTypeOption); - rootCommand.AddOption(streamsOption); - rootCommand.AddOption(enableCertAuthOption); - rootCommand.AddOption(deadlineOption); - rootCommand.AddOption(winHttpHandlerOption); - - rootCommand.SetHandler(async (InvocationContext context) => + rootCommand.Add(urlOption); + rootCommand.Add(udsFileNameOption); + rootCommand.Add(namedPipeNameOption); + rootCommand.Add(connectionsOption); + rootCommand.Add(warmupOption); + rootCommand.Add(durationOption); + rootCommand.Add(callCountOption); + rootCommand.Add(scenarioOption); + rootCommand.Add(latencyOption); + rootCommand.Add(protocolOption); + rootCommand.Add(logOption); + rootCommand.Add(requestSizeOption); + rootCommand.Add(responseSizeOption); + rootCommand.Add(grpcClientTypeOption); + rootCommand.Add(streamsOption); + rootCommand.Add(enableCertAuthOption); + rootCommand.Add(deadlineOption); + rootCommand.Add(winHttpHandlerOption); + + rootCommand.SetAction(async (ParseResult context) => { _options = new ClientOptions(); - _options.Url = context.ParseResult.GetValueForOption(urlOption); - _options.UdsFileName = context.ParseResult.GetValueForOption(udsFileNameOption); - _options.NamedPipeName = context.ParseResult.GetValueForOption(namedPipeNameOption); - _options.Connections = context.ParseResult.GetValueForOption(connectionsOption); - _options.Warmup = context.ParseResult.GetValueForOption(warmupOption); - _options.Duration = context.ParseResult.GetValueForOption(durationOption); - _options.CallCount = context.ParseResult.GetValueForOption(callCountOption); - _options.Scenario = context.ParseResult.GetValueForOption(scenarioOption); - _options.Latency = context.ParseResult.GetValueForOption(latencyOption); - _options.Protocol = context.ParseResult.GetValueForOption(protocolOption); - _options.LogLevel = context.ParseResult.GetValueForOption(logOption); - _options.RequestSize = context.ParseResult.GetValueForOption(requestSizeOption); - _options.ResponseSize = context.ParseResult.GetValueForOption(responseSizeOption); - _options.GrpcClientType = context.ParseResult.GetValueForOption(grpcClientTypeOption); - _options.Streams = context.ParseResult.GetValueForOption(streamsOption); - _options.EnableCertAuth = context.ParseResult.GetValueForOption(enableCertAuthOption); - _options.Deadline = context.ParseResult.GetValueForOption(deadlineOption); - _options.WinHttpHandler = context.ParseResult.GetValueForOption(winHttpHandlerOption); + _options.Url = context.GetValue(urlOption); + _options.UdsFileName = context.GetValue(udsFileNameOption); + _options.NamedPipeName = context.GetValue(namedPipeNameOption); + _options.Connections = context.GetValue(connectionsOption); + _options.Warmup = context.GetValue(warmupOption); + _options.Duration = context.GetValue(durationOption); + _options.CallCount = context.GetValue(callCountOption); + _options.Scenario = context.GetValue(scenarioOption); + _options.Latency = context.GetValue(latencyOption); + _options.Protocol = context.GetValue(protocolOption); + _options.LogLevel = context.GetValue(logOption); + _options.RequestSize = context.GetValue(requestSizeOption); + _options.ResponseSize = context.GetValue(responseSizeOption); + _options.GrpcClientType = context.GetValue(grpcClientTypeOption); + _options.Streams = context.GetValue(streamsOption); + _options.EnableCertAuth = context.GetValue(enableCertAuthOption); + _options.Deadline = context.GetValue(deadlineOption); + _options.WinHttpHandler = context.GetValue(winHttpHandlerOption); var runtimeVersion = typeof(object).GetTypeInfo().Assembly.GetCustomAttribute()?.InformationalVersion ?? "Unknown"; var isServerGC = GCSettings.IsServerGC; @@ -160,7 +160,8 @@ public static async Task Main(string[] args) Log("gRPC Client"); - return await rootCommand.InvokeAsync(args); + var result = rootCommand.Parse(args); + return await result.InvokeAsync(); } #if NET9_0_OR_GREATER diff --git a/src/dotnet-grpc/Commands/AddFileCommand.cs b/src/dotnet-grpc/Commands/AddFileCommand.cs index dd6e79b48..62508e2e0 100644 --- a/src/dotnet-grpc/Commands/AddFileCommand.cs +++ b/src/dotnet-grpc/Commands/AddFileCommand.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -25,7 +25,7 @@ namespace Grpc.Dotnet.Cli.Commands; internal sealed class AddFileCommand : CommandBase { - public AddFileCommand(IConsole console, string? projectPath, HttpClient httpClient) + public AddFileCommand(ConsoleService console, string? projectPath, HttpClient httpClient) : base(console, projectPath, httpClient) { } public static Command Create(HttpClient httpClient) @@ -38,40 +38,40 @@ public static Command Create(HttpClient httpClient) var serviceOption = CommonOptions.ServiceOption(); var additionalImportDirsOption = CommonOptions.AdditionalImportDirsOption(); var accessOption = CommonOptions.AccessOption(); - var filesArgument = new Argument + var filesArgument = new Argument("files") { - Name = "files", Description = CoreStrings.AddFileCommandArgumentDescription, Arity = ArgumentArity.OneOrMore }; - command.AddOption(projectOption); - command.AddOption(serviceOption); - command.AddOption(accessOption); - command.AddOption(additionalImportDirsOption); - command.AddArgument(filesArgument); + command.Add(projectOption); + command.Add(serviceOption); + command.Add(accessOption); + command.Add(additionalImportDirsOption); + command.Add(filesArgument); - command.SetHandler( + command.SetAction( async (context) => { - var project = context.ParseResult.GetValueForOption(projectOption); - var services = context.ParseResult.GetValueForOption(serviceOption); - var access = context.ParseResult.GetValueForOption(accessOption); - var additionalImportDirs = context.ParseResult.GetValueForOption(additionalImportDirsOption); - var files = context.ParseResult.GetValueForArgument(filesArgument); + var project = context.GetValue(projectOption); + var services = context.GetValue(serviceOption); + var access = context.GetValue(accessOption); + var additionalImportDirs = context.GetValue(additionalImportDirsOption); + var files = context.GetValue(filesArgument) ?? []; + var console = new ConsoleService(context.InvocationConfiguration.Output, context.InvocationConfiguration.Error); try { - var command = new AddFileCommand(context.Console, project, httpClient); + var command = new AddFileCommand(console, project, httpClient); await command.AddFileAsync(services, access, additionalImportDirs, files); - context.ExitCode = 0; + return 0; } catch (CLIToolException e) { - context.Console.LogError(e); + console.LogError(e); - context.ExitCode = -1; + return -1; } }); diff --git a/src/dotnet-grpc/Commands/AddUrlCommand.cs b/src/dotnet-grpc/Commands/AddUrlCommand.cs index 99e9b0ebf..9fc6121e9 100644 --- a/src/dotnet-grpc/Commands/AddUrlCommand.cs +++ b/src/dotnet-grpc/Commands/AddUrlCommand.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -25,11 +25,11 @@ namespace Grpc.Dotnet.Cli.Commands; internal sealed class AddUrlCommand : CommandBase { - public AddUrlCommand(IConsole console, string? projectPath, HttpClient httpClient) + public AddUrlCommand(ConsoleService console, string? projectPath, HttpClient httpClient) : base(console, projectPath, httpClient) { } // Internal for testing - internal AddUrlCommand(IConsole console, HttpClient client) + internal AddUrlCommand(ConsoleService console, HttpClient client) : base(console, client) { } public static Command Create(HttpClient httpClient) @@ -42,33 +42,34 @@ public static Command Create(HttpClient httpClient) var serviceOption = CommonOptions.ServiceOption(); var additionalImportDirsOption = CommonOptions.AdditionalImportDirsOption(); var accessOption = CommonOptions.AccessOption(); - var outputOption = new Option( - aliases: new[] { "-o", "--output" }, - description: CoreStrings.OutputOptionDescription); - var urlArgument = new Argument + var outputOption = new Option("--output", ["-o"]) + { + Description = CoreStrings.OutputOptionDescription + }; + var urlArgument = new Argument("url") { - Name = "url", Description = CoreStrings.AddUrlCommandArgumentDescription, Arity = ArgumentArity.ExactlyOne }; - command.AddOption(outputOption); - command.AddOption(projectOption); - command.AddOption(serviceOption); - command.AddOption(additionalImportDirsOption); - command.AddOption(accessOption); - command.AddArgument(urlArgument); + command.Add(outputOption); + command.Add(projectOption); + command.Add(serviceOption); + command.Add(additionalImportDirsOption); + command.Add(accessOption); + command.Add(urlArgument); - command.SetHandler( + command.SetAction( async (context) => { - var project = context.ParseResult.GetValueForOption(projectOption); - var services = context.ParseResult.GetValueForOption(serviceOption); - var access = context.ParseResult.GetValueForOption(accessOption); - var additionalImportDirs = context.ParseResult.GetValueForOption(additionalImportDirsOption); - var output = context.ParseResult.GetValueForOption(outputOption); - var url = context.ParseResult.GetValueForArgument(urlArgument); - + var project = context.GetValue(projectOption); + var services = context.GetValue(serviceOption); + var access = context.GetValue(accessOption); + var additionalImportDirs = context.GetValue(additionalImportDirsOption); + var output = context.GetValue(outputOption); + var url = context.GetRequiredValue(urlArgument); + + var console = new ConsoleService(context.InvocationConfiguration.Output, context.InvocationConfiguration.Error); try { if (string.IsNullOrEmpty(output)) @@ -76,16 +77,16 @@ public static Command Create(HttpClient httpClient) throw new CLIToolException(CoreStrings.ErrorNoOutputProvided); } - var command = new AddUrlCommand(context.Console, project, httpClient); + var command = new AddUrlCommand(console, project, httpClient); await command.AddUrlAsync(services, access, additionalImportDirs, url, output); - context.ExitCode = 0; + return 0; } catch (CLIToolException e) { - context.Console.LogError(e); + console.LogError(e); - context.ExitCode = -1; + return -1; } }); diff --git a/src/dotnet-grpc/Commands/CommandBase.cs b/src/dotnet-grpc/Commands/CommandBase.cs index ce56115da..d633c5746 100644 --- a/src/dotnet-grpc/Commands/CommandBase.cs +++ b/src/dotnet-grpc/Commands/CommandBase.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -16,7 +16,6 @@ #endregion -using System.CommandLine; using System.Diagnostics; using System.Globalization; using System.Reflection; @@ -47,24 +46,24 @@ internal class CommandBase private readonly HttpClient _httpClient; - public CommandBase(IConsole console, string? projectPath, HttpClient client) + public CommandBase(ConsoleService console, string? projectPath, HttpClient client) : this(console, ResolveProject(projectPath), client) { } // Internal for testing - internal CommandBase(IConsole console, Project project) + internal CommandBase(ConsoleService console, Project project) : this(console, project, new HttpClient()) { } - public CommandBase(IConsole console, HttpClient httpClient) + public CommandBase(ConsoleService console, HttpClient httpClient) : this(console, ResolveProject(null), httpClient) { } - internal CommandBase(IConsole console, Project project, HttpClient httpClient) + internal CommandBase(ConsoleService console, Project project, HttpClient httpClient) { Console = console; Project = project; _httpClient = httpClient; } - internal IConsole Console { get; set; } + internal ConsoleService Console { get; set; } internal Project Project { get; set; } private bool IsUsingWebSdk => Project.AllEvaluatedProperties.Any(p => string.Equals(UsingWebSDKPropertyName, p.Name, StringComparison.OrdinalIgnoreCase) && string.Equals("true", p.UnevaluatedValue, StringComparison.OrdinalIgnoreCase)); diff --git a/src/dotnet-grpc/Commands/ListCommand.cs b/src/dotnet-grpc/Commands/ListCommand.cs index 99056aec4..87bb6369b 100644 --- a/src/dotnet-grpc/Commands/ListCommand.cs +++ b/src/dotnet-grpc/Commands/ListCommand.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -17,8 +17,6 @@ #endregion using System.CommandLine; -using System.CommandLine.Rendering; -using System.CommandLine.Rendering.Views; using Grpc.Dotnet.Cli.Internal; using Grpc.Dotnet.Cli.Options; using Grpc.Dotnet.Cli.Properties; @@ -28,7 +26,7 @@ namespace Grpc.Dotnet.Cli.Commands; internal sealed class ListCommand : CommandBase { - public ListCommand(IConsole console, string? projectPath, HttpClient httpClient) + public ListCommand(ConsoleService console, string? projectPath, HttpClient httpClient) : base(console, projectPath, httpClient) { } public static Command Create(HttpClient httpClient) @@ -38,33 +36,44 @@ public static Command Create(HttpClient httpClient) description: CoreStrings.ListCommandDescription); var projectOption = CommonOptions.ProjectOption(); - command.AddOption(projectOption); + command.Add(projectOption); - command.SetHandler( + command.SetAction( (context) => { - var project = context.ParseResult.GetValueForOption(projectOption); + var project = context.GetValue(projectOption); + + var console = new ConsoleService(context.InvocationConfiguration.Output, context.InvocationConfiguration.Error); try { - var command = new ListCommand(context.Console, project, httpClient); + var command = new ListCommand(console, project, httpClient); command.List(); - context.ExitCode = 0; + return 0; } catch (CLIToolException e) { - context.Console.LogError(e); + console.LogError(e); - context.ExitCode = -1; + return -1; } }); return command; } + private class ListProtobufElement + { + public required string ProtobufReference { get; init; } + public required string ServiceType { get; init; } + + public string? SourceUrl { get; init; } + public string? Access { get; init; } + public string? AdditionalImportDirs { get; init; } + } + public void List() { - var consoleRenderer = new ConsoleRenderer(Console); var protobufElements = Project.GetItems(ProtobufElement).ToList(); if (protobufElements.Count == 0) { @@ -72,57 +81,37 @@ public void List() return; } - var table = new TableView { Items = protobufElements }; - - // Required columns (always displayed) - table.AddColumn(r => r.UnevaluatedInclude, CoreStrings.TableColumnProtobufReference); - table.AddColumn(r => - { - var serviceType = r.GetMetadataValue(GrpcServicesElement); - return string.IsNullOrEmpty(serviceType) ? "Both" : serviceType; - }, CoreStrings.TableColumnServiceType); - - // Optional columns (only displayed if an element is not default) - if (protobufElements.Any(r => !string.IsNullOrEmpty(r.GetMetadataValue(SourceUrlElement)))) - { - table.AddColumn(r => r.GetMetadataValue(SourceUrlElement), CoreStrings.TableColumnSourceUrl); - } - - // The default value is Public set by Grpc.Tools so skip this column if everything is default - if (protobufElements.Any(r => !string.Equals(r.GetMetadataValue(AccessElement), Access.Public.ToString(), StringComparison.OrdinalIgnoreCase))) + var typedProtobufElements = protobufElements.Select(e => new ListProtobufElement { - table.AddColumn(r => r.GetMetadataValue(AccessElement), CoreStrings.TableColumnAccess); - } - - if (protobufElements.Any(r => !string.IsNullOrEmpty(r.GetMetadataValue(AdditionalImportDirsElement)))) + ProtobufReference = e.UnevaluatedInclude, + ServiceType = e.GetMetadataValue(GrpcServicesElement) is { Length: > 0 } serviceType ? serviceType : "Both", + SourceUrl = e.GetMetadataValue(SourceUrlElement), + Access = e.GetMetadataValue(AccessElement), + AdditionalImportDirs = e.GetMetadataValue(AdditionalImportDirsElement) + }).ToList(); + + foreach (var element in typedProtobufElements) { - table.AddColumn(r => r.GetMetadataValue(AdditionalImportDirsElement), CoreStrings.TableColumnAdditionalImports); - } + // Required columns (always displayed) + Console.Log("{0}: {1}", CoreStrings.TableColumnProtobufReference, element.ProtobufReference); + Console.Log("{0}: {1}", CoreStrings.TableColumnServiceType, element.ServiceType); - var screen = new ScreenView(consoleRenderer, Console) { Child = table }; - Region region; - try - { - // Some environments incorrectly report zero width when there is no console - var width = System.Console.WindowWidth; - if (width == 0) + // Optional columns (only displayed if an element is not default) + if (!string.IsNullOrEmpty(element.SourceUrl)) { - width = int.MaxValue; + Console.Log("{0}: {1}", CoreStrings.TableColumnSourceUrl, element.SourceUrl); } - - var height = System.Console.WindowHeight; - if (height == 0) + // The default value is Public set by Grpc.Tools so skip this column if everything is default + if (!string.IsNullOrEmpty(element.Access) && !string.Equals(element.Access, Access.Public.ToString(), StringComparison.OrdinalIgnoreCase)) + { + Console.Log("{0}: {1}", CoreStrings.TableColumnAccess, element.Access); + } + if (!string.IsNullOrEmpty(element.AdditionalImportDirs)) { - height = int.MaxValue; + Console.Log("{0}: {1}", CoreStrings.TableColumnAdditionalImports, element.AdditionalImportDirs); } - region = new Region(0, 0, width, height); - } - catch (IOException) - { - // System.Console.WindowWidth can throw an IOException when runnning without a console attached - region = new Region(0, 0, int.MaxValue, int.MaxValue); + Console.Log(string.Empty); } - screen.Child?.Render(consoleRenderer, region); } } diff --git a/src/dotnet-grpc/Commands/RefreshCommand.cs b/src/dotnet-grpc/Commands/RefreshCommand.cs index be5522748..5f828b4ae 100644 --- a/src/dotnet-grpc/Commands/RefreshCommand.cs +++ b/src/dotnet-grpc/Commands/RefreshCommand.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -25,11 +25,11 @@ namespace Grpc.Dotnet.Cli.Commands; internal sealed class RefreshCommand : CommandBase { - public RefreshCommand(IConsole console, string? projectPath, HttpClient httpClient) + public RefreshCommand(ConsoleService console, string? projectPath, HttpClient httpClient) : base(console, projectPath, httpClient) { } // Internal for testing - public RefreshCommand(IConsole console, HttpClient client) + public RefreshCommand(ConsoleService console, HttpClient client) : base(console, client) { } public static Command Create(HttpClient httpClient) @@ -39,40 +39,40 @@ public static Command Create(HttpClient httpClient) description: CoreStrings.RefreshCommandDescription); var projectOption = CommonOptions.ProjectOption(); - var dryRunOption = new Option( - aliases: new[] { "--dry-run" }, - description: CoreStrings.DryRunOptionDescription - ); - var referencesArgument = new Argument + var dryRunOption = new Option("--dry-run") + { + Description = CoreStrings.DryRunOptionDescription + }; + var referencesArgument = new Argument("references") { - Name = "references", Description = CoreStrings.RefreshCommandArgumentDescription, Arity = ArgumentArity.ZeroOrMore }; - command.AddOption(projectOption); - command.AddOption(dryRunOption); - command.AddArgument(referencesArgument); + command.Add(projectOption); + command.Add(dryRunOption); + command.Add(referencesArgument); - command.SetHandler( + command.SetAction( async (context) => { - var project = context.ParseResult.GetValueForOption(projectOption); - var dryRun = context.ParseResult.GetValueForOption(dryRunOption); - var references = context.ParseResult.GetValueForArgument(referencesArgument); - + var project = context.GetValue(projectOption); + var dryRun = context.GetValue(dryRunOption); + var references = context.GetValue(referencesArgument) ?? []; + + var console = new ConsoleService(context.InvocationConfiguration.Output, context.InvocationConfiguration.Error); try { - var command = new RefreshCommand(context.Console, project, httpClient); + var command = new RefreshCommand(console, project, httpClient); await command.RefreshAsync(dryRun, references); - context.ExitCode = 0; + return 0; } catch (CLIToolException e) { - context.Console.LogError(e); + console.LogError(e); - context.ExitCode = -1; + return -1; } }); diff --git a/src/dotnet-grpc/Commands/RemoveCommand.cs b/src/dotnet-grpc/Commands/RemoveCommand.cs index 3bfc7d11e..afcccf0c4 100644 --- a/src/dotnet-grpc/Commands/RemoveCommand.cs +++ b/src/dotnet-grpc/Commands/RemoveCommand.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -25,7 +25,7 @@ namespace Grpc.Dotnet.Cli.Commands; internal sealed class RemoveCommand : CommandBase { - public RemoveCommand(IConsole console, string? projectPath, HttpClient httpClient) + public RemoveCommand(ConsoleService console, string? projectPath, HttpClient httpClient) : base(console, projectPath, httpClient) { } public static Command Create(HttpClient httpClient) @@ -35,36 +35,35 @@ public static Command Create(HttpClient httpClient) description: CoreStrings.RemoveCommandDescription); var projectOption = CommonOptions.ProjectOption(); - var referencesArgument = new Argument + var referencesArgument = new Argument("references") { - Name = "references", Description = CoreStrings.RemoveCommandArgumentDescription, Arity = ArgumentArity.OneOrMore }; - - command.AddOption(projectOption); - command.AddArgument(referencesArgument); - command.SetHandler( + command.Add(projectOption); + command.Add(referencesArgument); + + command.SetAction( (context) => { - var project = context.ParseResult.GetValueForOption(projectOption); - var references = context.ParseResult.GetValueForArgument(referencesArgument); + var project = context.GetValue(projectOption); + var references = context.GetValue(referencesArgument) ?? []; + + var console = new ConsoleService(context.InvocationConfiguration.Output, context.InvocationConfiguration.Error); try { - var command = new RemoveCommand(context.Console, project, httpClient); + var command = new RemoveCommand(console, project, httpClient); command.Remove(references); - context.ExitCode = 0; + return 0; } catch (CLIToolException e) { - context.Console.LogError(e); + console.LogError(e); - context.ExitCode = -1; + return -1; } - - return Task.CompletedTask; }); return command; diff --git a/src/dotnet-grpc/Internal/ConsoleService.cs b/src/dotnet-grpc/Internal/ConsoleService.cs new file mode 100644 index 000000000..c8ed130fe --- /dev/null +++ b/src/dotnet-grpc/Internal/ConsoleService.cs @@ -0,0 +1,33 @@ +#region Copyright notice and license + +// Copyright 2019 The gRPC Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#endregion + +namespace Grpc.Dotnet.Cli.Internal; + +internal sealed class ConsoleService +{ + public static readonly ConsoleService Null = new ConsoleService(TextWriter.Null, TextWriter.Null); + + public ConsoleService(TextWriter output, TextWriter error) + { + Output = output; + Error = error; + } + + public TextWriter Output { get; } + public TextWriter Error { get; } +} diff --git a/src/dotnet-grpc/Internal/ConsoleExtensions.cs b/src/dotnet-grpc/Internal/ConsoleServiceExtensions.cs similarity index 56% rename from src/dotnet-grpc/Internal/ConsoleExtensions.cs rename to src/dotnet-grpc/Internal/ConsoleServiceExtensions.cs index aaee06e8e..948c6d60d 100644 --- a/src/dotnet-grpc/Internal/ConsoleExtensions.cs +++ b/src/dotnet-grpc/Internal/ConsoleServiceExtensions.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -16,24 +16,23 @@ #endregion -using System.CommandLine; using System.Globalization; namespace Grpc.Dotnet.Cli.Internal; -internal static class ConsoleExtensions +internal static class ConsoleServiceExtensions { - public static void Log(this IConsole console, string formatString, params string[] args) + public static void Log(this ConsoleService console, string formatString, params string[] args) { - console.Out.Write(string.Format(CultureInfo.CurrentCulture, formatString, args) + Environment.NewLine); + console.Output.Write(string.Format(CultureInfo.CurrentCulture, formatString, args) + Environment.NewLine); } - public static void LogWarning(this IConsole console, string formatString, params string[] args) + public static void LogWarning(this ConsoleService console, string formatString, params string[] args) { - console.Out.Write(string.Format(CultureInfo.CurrentCulture, $"Warning: {formatString}", args) + Environment.NewLine); + console.Output.Write(string.Format(CultureInfo.CurrentCulture, $"Warning: {formatString}", args) + Environment.NewLine); } - public static void LogError(this IConsole console, Exception e) + public static void LogError(this ConsoleService console, Exception e) { console.Error.Write($"Error: {e.Message}" + Environment.NewLine); } diff --git a/src/dotnet-grpc/Options/CommonOptions.cs b/src/dotnet-grpc/Options/CommonOptions.cs index 0a5f4e3f9..186786c5f 100644 --- a/src/dotnet-grpc/Options/CommonOptions.cs +++ b/src/dotnet-grpc/Options/CommonOptions.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -25,33 +25,37 @@ internal static class CommonOptions { public static Option ProjectOption() { - var o = new Option( - aliases: new[] { "-p", "--project" }, - description: CoreStrings.ProjectOptionDescription); + var o = new Option("--project", ["-p"]) + { + Description = CoreStrings.ProjectOptionDescription + }; return o; } public static Option ServiceOption() { - var o = new Option( - aliases: new[] { "-s", "--services" }, - description: CoreStrings.ServiceOptionDescription); + var o = new Option("--services", ["-s"]) + { + Description = CoreStrings.ServiceOptionDescription + }; return o; } public static Option AccessOption() { - var o = new Option( - aliases: new[] { "--access" }, - description: CoreStrings.AccessOptionDescription); + var o = new Option("--access") + { + Description = CoreStrings.AccessOptionDescription + }; return o; } public static Option AdditionalImportDirsOption() { - var o = new Option( - aliases: new[] { "-i", "--additional-import-dirs" }, - description: CoreStrings.AdditionalImportDirsOption); + var o = new Option("--additional-import-dirs", ["-i"]) + { + Description = CoreStrings.AdditionalImportDirsOption + }; return o; } } diff --git a/src/dotnet-grpc/Program.cs b/src/dotnet-grpc/Program.cs index 1a57ff7fb..2fe1fc622 100644 --- a/src/dotnet-grpc/Program.cs +++ b/src/dotnet-grpc/Program.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -17,9 +17,6 @@ #endregion using System.CommandLine; -using System.CommandLine.Builder; -using System.CommandLine.IO; -using System.CommandLine.Parsing; using Grpc.Dotnet.Cli.Commands; using Microsoft.Build.Locator; @@ -31,24 +28,23 @@ public static Task Main(string[] args) { MSBuildLocator.RegisterDefaults(); - var parser = BuildParser(new HttpClient()); - var result = parser.Parse(args); + var rootCommand = BuildRootCommand(new HttpClient()); + var result = rootCommand.Parse(args); - return result.InvokeAsync(new SystemConsole()); + return result.InvokeAsync(); } - internal static Parser BuildParser(HttpClient client) + internal static RootCommand BuildRootCommand(HttpClient client) { - var root = new RootCommand(); - root.AddCommand(AddFileCommand.Create(client)); - root.AddCommand(AddUrlCommand.Create(client)); - root.AddCommand(RefreshCommand.Create(client)); - root.AddCommand(RemoveCommand.Create(client)); - root.AddCommand(ListCommand.Create(client)); - - var parser = new CommandLineBuilder(root) - .UseDefaults() - .Build(); - return parser; + var root = new RootCommand + { + AddFileCommand.Create(client), + AddUrlCommand.Create(client), + RefreshCommand.Create(client), + RemoveCommand.Create(client), + ListCommand.Create(client) + }; + + return root; } } diff --git a/src/dotnet-grpc/dotnet-grpc.csproj b/src/dotnet-grpc/dotnet-grpc.csproj index 3625ad820..0a5316f61 100644 --- a/src/dotnet-grpc/dotnet-grpc.csproj +++ b/src/dotnet-grpc/dotnet-grpc.csproj @@ -24,7 +24,7 @@ - + diff --git a/test/FunctionalTests/Infrastructure/InProcessTestServer.cs b/test/FunctionalTests/Infrastructure/InProcessTestServer.cs index 4d8c62c6e..c8abff668 100644 --- a/test/FunctionalTests/Infrastructure/InProcessTestServer.cs +++ b/test/FunctionalTests/Infrastructure/InProcessTestServer.cs @@ -32,7 +32,7 @@ public abstract class InProcessTestServer : IDisposable public abstract string GetUrl(TestServerEndpointName endpointName); - public abstract IWebHost? Host { get; } + public abstract IHost? Host { get; } public abstract void StartServer(); @@ -47,7 +47,7 @@ public class InProcessTestServer : InProcessTestServer private readonly LogSinkProvider _logSinkProvider; private readonly Action _initialConfigureServices; private readonly Action> _configureKestrel; - private IWebHost? _host; + private IHost? _host; private IHostApplicationLifetime? _lifetime; private Dictionary? _urls; @@ -67,7 +67,7 @@ public override string GetUrl(TestServerEndpointName endpointName) return _urls[endpointName].Address; } - public override IWebHost? Host => _host; + public override IHost? Host => _host; public InProcessTestServer(Action initialConfigureServices, Action> configureKestrel) { @@ -84,20 +84,24 @@ public override void StartServer() { _urls = new Dictionary(); - var builder = new WebHostBuilder() + var builder = new HostBuilder() + .ConfigureWebHost(configure => + { + configure + .UseStartup(typeof(TStartup)) + .UseKestrel((context, options) => + { + _configureKestrel(context, options, _urls); + }) + .UseContentRoot(Directory.GetCurrentDirectory()); + }) .ConfigureLogging(builder => builder .SetMinimumLevel(LogLevel.Trace) .AddProvider(new ForwardingLoggerProvider(_loggerFactory))) .ConfigureServices(services => { _initialConfigureServices?.Invoke(services); - }) - .UseStartup(typeof(TStartup)) - .UseKestrel((context, options) => - { - _configureKestrel(context, options, _urls); - }) - .UseContentRoot(Directory.GetCurrentDirectory()); + }); _host = builder.Build(); diff --git a/test/dotnet-grpc.Tests/AddFileCommandTests.cs b/test/dotnet-grpc.Tests/AddFileCommandTests.cs index 9f9d07b2b..de356b9b0 100644 --- a/test/dotnet-grpc.Tests/AddFileCommandTests.cs +++ b/test/dotnet-grpc.Tests/AddFileCommandTests.cs @@ -16,9 +16,10 @@ #endregion -using System.CommandLine.IO; +using System.CommandLine; using System.CommandLine.Parsing; using Grpc.Dotnet.Cli.Commands; +using Grpc.Dotnet.Cli.Internal; using Grpc.Dotnet.Cli.Options; using Microsoft.Build.Evaluation; using NUnit.Framework; @@ -35,16 +36,17 @@ public async Task Commandline_AddFileCommand_AddsPackagesAndReferences() // Arrange var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var testConsole = new TestConsole(); + var errorOut = new StringWriter(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "EmptyProject")).CopyTo(tempDir); - var parser = Program.BuildParser(CreateClient()); + var rootCommand = Program.BuildRootCommand(CreateClient()); // Act - var result = await parser.InvokeAsync($"add-file -p {tempDir} -s Server --access Internal -i ImportDir {Path.Combine("Proto", "*.proto")}", testConsole); + var result = rootCommand.Parse($"add-file -p {tempDir} -s Server --access Internal -i ImportDir {Path.Combine("Proto", "*.proto")}"); + var errorCode = await result.InvokeAsync(configuration: new InvocationConfiguration { Error = errorOut }); // Assert - Assert.AreEqual(0, result, testConsole.Error.ToString()!); + Assert.AreEqual(0, errorCode, errorOut.ToString()); var project = ProjectCollection.GlobalProjectCollection.LoadedProjects.Single(p => p.DirectoryPath == tempDir); project.ReevaluateIfNecessary(); @@ -79,7 +81,7 @@ public async Task AddFileCommand_AddsPackagesAndReferences() // Act Directory.SetCurrentDirectory(tempDir); - var command = new AddFileCommand(new TestConsole(), projectPath: null, CreateClient()); + var command = new AddFileCommand(ConsoleService.Null, projectPath: null, CreateClient()); await command.AddFileAsync(Services.Server, Access.Internal, "ImportDir", new[] { Path.Combine("Proto", "*.proto") }); command.Project.ReevaluateIfNecessary(); @@ -88,7 +90,6 @@ public async Task AddFileCommand_AddsPackagesAndReferences() Assert.AreEqual(1, packageRefs.Count); Assert.NotNull(packageRefs.SingleOrDefault(r => r.UnevaluatedInclude == "Grpc.AspNetCore" && !r.HasMetadata(CommandBase.PrivateAssetsElement))); - var protoRefs = command.Project.GetItems(CommandBase.ProtobufElement); Assert.AreEqual(2, protoRefs.Count); Assert.NotNull(protoRefs.SingleOrDefault(r => r.UnevaluatedInclude == "Proto\\a.proto")); diff --git a/test/dotnet-grpc.Tests/AddUrlCommandTests.cs b/test/dotnet-grpc.Tests/AddUrlCommandTests.cs index 520eb326c..8f629d82d 100644 --- a/test/dotnet-grpc.Tests/AddUrlCommandTests.cs +++ b/test/dotnet-grpc.Tests/AddUrlCommandTests.cs @@ -16,7 +16,7 @@ #endregion -using System.CommandLine.IO; +using System.CommandLine; using System.CommandLine.Parsing; using Grpc.Dotnet.Cli.Commands; using Grpc.Dotnet.Cli.Internal; @@ -37,16 +37,17 @@ public async Task Commandline_AddUrlCommand_AddsPackagesAndReferences() // Arrange var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var testConsole = new TestConsole(); + var errorWriter = new StringWriter(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "EmptyProject")).CopyTo(tempDir); - var parser = Program.BuildParser(CreateClient()); + var rootCommand = Program.BuildRootCommand(CreateClient()); // Act - var result = await parser.InvokeAsync($"add-url -p {tempDir} -s Server --access Internal -i ImportDir -o {Path.Combine("Proto", "c.proto")} {SourceUrl}", testConsole); + var result = rootCommand.Parse($"add-url -p {tempDir} -s Server --access Internal -i ImportDir -o {Path.Combine("Proto", "c.proto")} {SourceUrl}"); + var errorCode = await result.InvokeAsync(configuration: new InvocationConfiguration { Error = errorWriter }); // Assert - Assert.AreEqual(0, result, testConsole.Error.ToString()!); + Assert.AreEqual(0, errorCode, errorWriter.ToString()); var project = ProjectCollection.GlobalProjectCollection.LoadedProjects.Single(p => p.DirectoryPath == tempDir); project.ReevaluateIfNecessary(); @@ -81,7 +82,7 @@ public async Task AddUrlCommand_AddsPackagesAndReferences() // Act Directory.SetCurrentDirectory(tempDir); - var command = new AddUrlCommand(new TestConsole(), CreateClient()); + var command = new AddUrlCommand(ConsoleService.Null, CreateClient()); await command.AddUrlAsync(Services.Server, Access.Internal, "ImportDir", SourceUrl, Path.Combine("Proto", "c.proto")); command.Project.ReevaluateIfNecessary(); @@ -116,7 +117,7 @@ public async Task AddUrlCommand_NoOutputSpecified_Error() // Act, Assert Directory.SetCurrentDirectory(tempDir); - var command = new AddUrlCommand(new TestConsole(), CreateClient()); + var command = new AddUrlCommand(ConsoleService.Null, CreateClient()); await ExceptionAssert.ThrowsAsync(() => command.AddUrlAsync(Services.Server, Access.Internal, "ImportDir", SourceUrl, string.Empty)).DefaultTimeout(); // Cleanup diff --git a/test/dotnet-grpc.Tests/CommandBaseTests.cs b/test/dotnet-grpc.Tests/CommandBaseTests.cs index 6249bf167..b957d5525 100644 --- a/test/dotnet-grpc.Tests/CommandBaseTests.cs +++ b/test/dotnet-grpc.Tests/CommandBaseTests.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -16,7 +16,6 @@ #endregion -using System.CommandLine.IO; using System.Globalization; using System.Reflection; using System.Runtime.InteropServices; @@ -34,7 +33,6 @@ namespace Grpc.Dotnet.Cli.Tests; [TestFixture] public class CommandBaseTests : TestBase { - [Test] public Task EnsureNugetPackages_AddsRequiredServerPackages_ForServer() => EnsureNugetPackages_AddsRequiredServerPackages(Services.Server); @@ -46,7 +44,7 @@ public Task EnsureNugetPackages_AddsRequiredServerPackages_ForBoth() private async Task EnsureNugetPackages_AddsRequiredServerPackages(Services services) { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); // Act await commandBase.EnsureNugetPackagesAsync(services); @@ -62,7 +60,7 @@ private async Task EnsureNugetPackages_AddsRequiredServerPackages(Services servi public async Task EnsureNugetPackages_AddsRequiredClientPackages_ForNonWebClient() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); // Act await commandBase.EnsureNugetPackagesAsync(Services.Client); @@ -80,9 +78,7 @@ public async Task EnsureNugetPackages_AddsRequiredClientPackages_ForNonWebClient public async Task EnsureNugetPackages_AddsRequiredClientPackages_ForWebClient() { // Arrange - var commandBase = new CommandBase( - new TestConsole(), - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); // Act await commandBase.EnsureNugetPackagesAsync(Services.Client); @@ -99,7 +95,7 @@ public async Task EnsureNugetPackages_AddsRequiredClientPackages_ForWebClient() public async Task EnsureNugetPackages_AddsRequiredNonePackages_ForNone() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); // Act await commandBase.EnsureNugetPackagesAsync(Services.None); @@ -116,7 +112,7 @@ public async Task EnsureNugetPackages_AddsRequiredNonePackages_ForNone() public async Task EnsureNugetPackages_DoesNotOverwriteExistingPackageReferences() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); commandBase.Project.AddItem(CommandBase.PackageReferenceElement, "Grpc.Tools"); // Act @@ -134,7 +130,7 @@ public async Task EnsureNugetPackages_DoesNotOverwriteExistingPackageReferences( public void AddProtobufReference_ThrowsIfFileNotFound() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); // Act, Assert Assert.Throws(() => commandBase.AddProtobufReference(Services.Both, string.Empty, Access.Public, "NonExistentFile", string.Empty)); @@ -166,9 +162,7 @@ static object[] ReferenceCases() public void AddProtobufReference_AddsRelativeReference(string path, string normalizedPath, string link) { // Arrange - var commandBase = new CommandBase( - new TestConsole(), - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); // Act commandBase.AddProtobufReference(Services.Server, "ImportDir", Access.Internal, path, SourceUrl); @@ -191,9 +185,7 @@ public void AddProtobufReference_AddsRelativeReference(string path, string norma public void AddProtobufReference_AddsAbsoluteReference(string path, string normalizedPath, string link) { // Arrange - var commandBase = new CommandBase( - new TestConsole(), - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); var referencePath = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", path); var normalizedReferencePath = Path.GetFullPath( @@ -245,9 +237,7 @@ static object[] AdditionalImportDirsCases() public void AddProtobufReference_AdditionalImportDirs(string additionalImportDir, string normalizedAdditionalImportDir) { // Arrange - var commandBase = new CommandBase( - new TestConsole(), - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); const string proto = "Proto/a.proto"; @@ -271,9 +261,7 @@ public void AddProtobufReference_AdditionalImportDirs(string additionalImportDir public void AddProtobufReference_Without_AdditionalImportDirs() { // Arrange - var commandBase = new CommandBase( - new TestConsole(), - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); const string proto = "Proto/a.proto"; @@ -318,7 +306,7 @@ static object[] DoesNotOverwriteCases() public void AddProtobufReference_DoesNotOverwriteReference(string path, string altPath, string normalizedPath) { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); var referencePath = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", path); var altReferencePath = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", altPath); var normalizedReferencePath = Path.GetFullPath( @@ -371,9 +359,7 @@ static object Case(params string[] segments) public void AddProtobufReference_AddsLinkElementIfFileOutsideProject(string path, string normalizedPath) { // Arrange - var commandBase = new CommandBase( - new TestConsole(), - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); // Act commandBase.AddProtobufReference(Services.Server, "ImportDir", Access.Internal, path, SourceUrl); @@ -460,7 +446,7 @@ public void ResolveServices_ReturnsIdentity_None() private void ResolveServices_ReturnsIdentity_IfNotDefault(Services services) { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project()); + var commandBase = CreateCommandBase(); // Act, Assert Assert.AreEqual(services, commandBase.ResolveServices(services)); @@ -470,7 +456,7 @@ private void ResolveServices_ReturnsIdentity_IfNotDefault(Services services) public void ResolveServices_ReturnsBoth_IfWebSDK() { // Arrange - var commandBase = new CommandBase(new TestConsole(), CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "DuplicateProjects", "server.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "DuplicateProjects", "server.csproj"))); // Act, Assert Assert.AreEqual(Services.Both, commandBase.ResolveServices(Services.Default)); @@ -480,7 +466,7 @@ public void ResolveServices_ReturnsBoth_IfWebSDK() public void ResolveServices_ReturnsClient_IfNotWebSDK() { // Arrange - var commandBase = new CommandBase(new TestConsole(), CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "DuplicateProjects", "client.csproj"))); + var commandBase = CreateCommandBase(project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "DuplicateProjects", "client.csproj"))); // Act, Assert Assert.AreEqual(Services.Client, commandBase.ResolveServices(Services.Default)); @@ -490,10 +476,11 @@ public void ResolveServices_ReturnsClient_IfNotWebSDK() public void GlobReferences_ExpandsRelativeReferences_WarnsIfReferenceNotResolved() { // Arrange - var testConsole = new TestConsole(); - var commandBase = new CommandBase( - testConsole, - CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); + var outWriter = new StringWriter(); + var console = new ConsoleService(outWriter, TextWriter.Null); + var commandBase = CreateCommandBase( + console: console, + project: CreateIsolatedProject(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "test.csproj"))); var invalidReference = Path.Combine("Proto", "invalid*reference.proto"); // Act @@ -502,15 +489,16 @@ public void GlobReferences_ExpandsRelativeReferences_WarnsIfReferenceNotResolved // Assert Assert.Contains(Path.Combine("Proto", "a.proto"), references); Assert.Contains(Path.Combine("Proto", "b.proto"), references); - Assert.AreEqual($"Warning: {string.Format(CultureInfo.InvariantCulture, CoreStrings.LogWarningNoReferenceResolved, invalidReference, SourceUrl)}", testConsole.Out.ToString()!.TrimEnd()); + Assert.AreEqual($"Warning: {string.Format(CultureInfo.InvariantCulture, CoreStrings.LogWarningNoReferenceResolved, invalidReference, SourceUrl)}", outWriter.ToString().TrimEnd()); } [Test] public void GlobReferences_ExpandsAbsoluteReferences_WarnsIfReferenceNotResolved() { // Arrange - var testConsole = new TestConsole(); - var commandBase = new CommandBase(testConsole, new Project()); + var outWriter = new StringWriter(); + var console = new ConsoleService(outWriter, TextWriter.Null); + var commandBase = CreateCommandBase(console: console); var invalidReference = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "invalid*reference.proto"); // Act @@ -519,7 +507,7 @@ public void GlobReferences_ExpandsAbsoluteReferences_WarnsIfReferenceNotResolved // Assert Assert.Contains(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "a.proto"), references); Assert.Contains(Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "b.proto"), references); - Assert.AreEqual($"Warning: {string.Format(CultureInfo.InvariantCulture, CoreStrings.LogWarningNoReferenceResolved, invalidReference, SourceUrl)}", testConsole.Out.ToString()!.TrimEnd()); + Assert.AreEqual($"Warning: {string.Format(CultureInfo.InvariantCulture, CoreStrings.LogWarningNoReferenceResolved, invalidReference, SourceUrl)}", outWriter.ToString().TrimEnd()); } private static readonly object[] DirectoryPaths = @@ -533,7 +521,7 @@ public void GlobReferences_ExpandsAbsoluteReferences_WarnsIfReferenceNotResolved public async Task DownloadFileAsync_DirectoryAsDestination_Throws(string destination) { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project(), CreateClient()); + var commandBase = new CommandBase(ConsoleService.Null, new Project(), CreateClient()); // Act, Assert await ExceptionAssert.ThrowsAsync(() => commandBase.DownloadFileAsync(SourceUrl, destination)).DefaultTimeout(); @@ -543,7 +531,7 @@ public async Task DownloadFileAsync_DirectoryAsDestination_Throws(string destina public async Task DownloadFileAsync_DownloadsRemoteFile() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project(), CreateClient()); + var commandBase = new CommandBase(ConsoleService.Null, new Project(), CreateClient()); var tempProtoFile = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "c.proto"); // Act @@ -558,7 +546,7 @@ public async Task DownloadFileAsync_DownloadsRemoteFile() public async Task DownloadFileAsync_DownloadsRemoteFile_OverwritesIfContentDoesNotMatch() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project(), CreateClient()); + var commandBase = new CommandBase(ConsoleService.Null, new Project(), CreateClient()); var tempProtoFile = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "c.proto"); // Act @@ -574,7 +562,7 @@ public async Task DownloadFileAsync_DownloadsRemoteFile_OverwritesIfContentDoesN public async Task DownloadFileAsync_DownloadsRemoteFile_SkipIfContentMatches() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project(), CreateClient()); + var commandBase = new CommandBase(ConsoleService.Null, new Project(), CreateClient()); var tempProtoFile = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "c.proto"); // Act @@ -591,7 +579,7 @@ public async Task DownloadFileAsync_DownloadsRemoteFile_SkipIfContentMatches() public async Task DownloadFileAsync_DownloadsRemoteFile_DoesNotOverwriteForDryrun() { // Arrange - var commandBase = new CommandBase(new TestConsole(), new Project(), CreateClient()); + var commandBase = new CommandBase(ConsoleService.Null, new Project(), CreateClient()); var tempProtoFile = Path.Combine(Directory.GetCurrentDirectory(), "TestAssets", "EmptyProject", "Proto", "c.proto"); // Act @@ -617,6 +605,11 @@ public void IsUrl_ChecksUrlValidity(string url, bool isUrl) private Project CreateIsolatedProject(string path) => Project.FromFile(path, new ProjectOptions { ProjectCollection = new ProjectCollection() }); + + private static CommandBase CreateCommandBase(ConsoleService? console = null, Project? project = null) + { + return new CommandBase(console ?? ConsoleService.Null, project ?? new Project()); + } } [TestFixture] @@ -646,7 +639,7 @@ public async Task EnsureNugetPackages_UsesVersionsFromRemoteFile_IfAvailable() }" } }; - var commandBase = new CommandBase(new TestConsole(), new Project(), CreateClient(content)); + var commandBase = new CommandBase(ConsoleService.Null, new Project(), CreateClient(content)); // Act await commandBase.EnsureNugetPackagesAsync(Services.Client); diff --git a/test/dotnet-grpc.Tests/ListCommandTests.cs b/test/dotnet-grpc.Tests/ListCommandTests.cs index 52ab6bfe6..0b6535821 100644 --- a/test/dotnet-grpc.Tests/ListCommandTests.cs +++ b/test/dotnet-grpc.Tests/ListCommandTests.cs @@ -16,9 +16,10 @@ #endregion -using System.CommandLine.IO; +using System.CommandLine; using System.CommandLine.Parsing; using Grpc.Dotnet.Cli.Commands; +using Grpc.Dotnet.Cli.Internal; using Microsoft.Build.Evaluation; using NUnit.Framework; @@ -32,41 +33,41 @@ public async Task Commandline_List_ListsReferences() { var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var outWriter = new StringWriter(); + var errorWriter = new StringWriter(); try { // Arrange - var testConsole = new TestConsole(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "MultipleReferences")).CopyTo(tempDir); - var parser = Program.BuildParser(CreateClient()); + var rootCommand = Program.BuildRootCommand(CreateClient()); // Act - var result = await parser.InvokeAsync($"list -p {tempDir}", testConsole); + var result = rootCommand.Parse($"list -p {tempDir}"); + var errorCode = await result.InvokeAsync(configuration: new InvocationConfiguration { Output = outWriter, Error = errorWriter }); // Assert - Assert.AreEqual(0, result, testConsole.Error.ToString()!); + Assert.AreEqual(0, errorCode, errorWriter.ToString()); var project = ProjectCollection.GlobalProjectCollection.LoadedProjects.Single(p => p.DirectoryPath == tempDir); project.ReevaluateIfNecessary(); - var output = testConsole.Out.ToString()!; - var lines = output.Split(Environment.NewLine); + var output = outWriter.ToString(); - // First line is the heading and should conatin Protobuf Reference, Service Type, Source URL, Access - AssertContains(lines[0], "Protobuf Reference"); - AssertContains(lines[0], "Service Type"); - AssertContains(lines[0], "Source URL"); - AssertContains(lines[0], "Access"); - - // Second line is the reference to // // https://contoso.com/greet.proto // - Assert.AreEqual(new string[] { "Proto/a.proto", "Both", "https://contoso.com/greet.proto" }, lines[1].Split(' ', StringSplitOptions.RemoveEmptyEntries)); - - // Third line is the reference to // - Assert.AreEqual(new string[] { "Proto/b.proto", "Both", "Internal" }, lines[2].Split(' ', StringSplitOptions.RemoveEmptyEntries)); + var expected = """ + Protobuf Reference: Proto/a.proto + Service Type: Both + Source URL: https://contoso.com/greet.proto + + Protobuf Reference: Proto/b.proto + Service Type: Both + Access: Internal + """; + Assert.AreEqual(expected.Trim(), output.Trim()); } finally { @@ -80,16 +81,17 @@ public void List_ListsReferences() { var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); + var outWriter = new StringWriter(); + var errorWriter = new StringWriter(); try { // Arrange - var testConsole = new TestConsole(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "MultipleReferences")).CopyTo(tempDir); // Act Directory.SetCurrentDirectory(tempDir); - var command = new ListCommand(testConsole, null, CreateClient()); + var command = new ListCommand(new ConsoleService(outWriter, errorWriter), null, CreateClient()); Assert.IsNotNull(command.Project); Assert.AreEqual("test.csproj", Path.GetFileName(command.Project.FullPath)); @@ -97,24 +99,22 @@ public void List_ListsReferences() command.List(); // Assert - var output = testConsole.Out.ToString()!; - var lines = output.Split(Environment.NewLine); + var output = outWriter.ToString(); - // First line is the heading and should conatin Protobuf Reference, Service Type, Source URL, Access - AssertContains(lines[0], "Protobuf Reference"); - AssertContains(lines[0], "Service Type"); - AssertContains(lines[0], "Source URL"); - AssertContains(lines[0], "Access"); - - // Second line is the reference to // // https://contoso.com/greet.proto // - Assert.AreEqual(new string[] { "Proto/a.proto", "Both", "https://contoso.com/greet.proto" }, lines[1].Split(' ', StringSplitOptions.RemoveEmptyEntries)); - - // Third line is the reference to // - Assert.AreEqual(new string[] { "Proto/b.proto", "Both", "Internal" }, lines[2].Split(' ', StringSplitOptions.RemoveEmptyEntries)); + var expected = """ + Protobuf Reference: Proto/a.proto + Service Type: Both + Source URL: https://contoso.com/greet.proto + + Protobuf Reference: Proto/b.proto + Service Type: Both + Access: Internal + """; + Assert.AreEqual(expected.Trim(), output.Trim()); } finally { @@ -123,9 +123,4 @@ public void List_ListsReferences() Directory.Delete(tempDir, true); } } - - private void AssertContains(string source, string s) - { - Assert.True(source.Contains(s), $"Source '{source}' does not contain '{s}'."); - } } diff --git a/test/dotnet-grpc.Tests/RefreshCommandTests.cs b/test/dotnet-grpc.Tests/RefreshCommandTests.cs index 0d9bdfc14..ab9828487 100644 --- a/test/dotnet-grpc.Tests/RefreshCommandTests.cs +++ b/test/dotnet-grpc.Tests/RefreshCommandTests.cs @@ -16,10 +16,11 @@ #endregion -using System.CommandLine.IO; +using System.CommandLine; using System.CommandLine.Parsing; using System.Globalization; using Grpc.Dotnet.Cli.Commands; +using Grpc.Dotnet.Cli.Internal; using Grpc.Dotnet.Cli.Properties; using Microsoft.Build.Evaluation; using NUnit.Framework; @@ -37,21 +38,23 @@ public async Task Commandline_Refresh_RefreshesReferences(bool dryRun) // Arrange var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var testConsole = new TestConsole(); + var outWriter = new StringWriter(); + var errorWriter = new StringWriter(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "ProjectWithReference")).CopyTo(tempDir); - var parser = Program.BuildParser(CreateClient()); + var rootCommand = Program.BuildRootCommand(CreateClient()); // Act - var result = await parser.InvokeAsync($"refresh -p {tempDir} --dry-run {dryRun}", testConsole); + var result = rootCommand.Parse($"refresh -p {tempDir} --dry-run {dryRun}"); + var errorCode = await result.InvokeAsync(configuration: new InvocationConfiguration { Output = outWriter, Error = errorWriter }); // Assert - Assert.AreEqual(0, result, testConsole.Error.ToString()!); + Assert.AreEqual(0, errorCode, errorWriter.ToString()); var project = ProjectCollection.GlobalProjectCollection.LoadedProjects.Single(p => p.DirectoryPath == tempDir); project.ReevaluateIfNecessary(); - Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, CoreStrings.LogDownload, "Proto/a.proto", SourceUrl), testConsole.Out.ToString()!.TrimEnd()); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, CoreStrings.LogDownload, "Proto/a.proto", SourceUrl), outWriter.ToString().TrimEnd()); Assert.AreEqual(dryRun, string.IsNullOrEmpty(File.ReadAllText(Path.Combine(project.DirectoryPath, "Proto", "a.proto")))); // Cleanup @@ -66,16 +69,16 @@ public async Task Refresh_RefreshesReferences(bool dryRun) // Arrange var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var testConsole = new TestConsole(); + var outWriter = new StringWriter(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "ProjectWithReference")).CopyTo(tempDir); // Act Directory.SetCurrentDirectory(tempDir); - var command = new RefreshCommand(testConsole, CreateClient()); + var command = new RefreshCommand(new ConsoleService(outWriter, TextWriter.Null), CreateClient()); await command.RefreshAsync(dryRun, Array.Empty()); // Assert - Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, CoreStrings.LogDownload, "Proto/a.proto", SourceUrl), testConsole.Out.ToString()!.TrimEnd()); + Assert.AreEqual(string.Format(CultureInfo.InvariantCulture, CoreStrings.LogDownload, "Proto/a.proto", SourceUrl), outWriter.ToString().TrimEnd()); Assert.AreEqual(dryRun, string.IsNullOrEmpty(File.ReadAllText(Path.Combine(command.Project.DirectoryPath, "Proto", "a.proto")))); // Cleanup diff --git a/test/dotnet-grpc.Tests/RemoveCommandTests.cs b/test/dotnet-grpc.Tests/RemoveCommandTests.cs index 889f7d010..994e8c906 100644 --- a/test/dotnet-grpc.Tests/RemoveCommandTests.cs +++ b/test/dotnet-grpc.Tests/RemoveCommandTests.cs @@ -16,9 +16,10 @@ #endregion -using System.CommandLine.IO; +using System.CommandLine; using System.CommandLine.Parsing; using Grpc.Dotnet.Cli.Commands; +using Grpc.Dotnet.Cli.Internal; using Microsoft.Build.Evaluation; using NUnit.Framework; @@ -34,16 +35,17 @@ public async Task Commandline_Remove_RemovesReferences() // Arrange var currentDir = Directory.GetCurrentDirectory(); var tempDir = Path.Combine(Path.GetTempPath(), Guid.NewGuid().ToString()); - var testConsole = new TestConsole(); + var errorWriter = new StringWriter(); new DirectoryInfo(Path.Combine(currentDir, "TestAssets", "ProjectWithReference")).CopyTo(tempDir); - var parser = Program.BuildParser(CreateClient()); + var rootCommand = Program.BuildRootCommand(CreateClient()); // Act - var result = await parser.InvokeAsync($"remove -p {tempDir} {Path.Combine("Proto", "a.proto")}", testConsole); + var result = rootCommand.Parse($"remove -p {tempDir} {Path.Combine("Proto", "a.proto")}"); + var errorCode = await result.InvokeAsync(configuration: new InvocationConfiguration { Error = errorWriter }); // Assert - Assert.AreEqual(0, result, testConsole.Error.ToString()!); + Assert.AreEqual(0, errorCode, errorWriter.ToString()); var project = ProjectCollection.GlobalProjectCollection.LoadedProjects.Single(p => p.DirectoryPath == tempDir); project.ReevaluateIfNecessary(); @@ -67,7 +69,7 @@ public void Remove_RemovesReferences() // Act Directory.SetCurrentDirectory(tempDir); - var command = new RemoveCommand(new TestConsole(), null, CreateClient()); + var command = new RemoveCommand(ConsoleService.Null, null, CreateClient()); command.Remove(new[] { Path.Combine("Proto", "a.proto") }); // Assert diff --git a/testassets/InteropTestsClient/Program.cs b/testassets/InteropTestsClient/Program.cs index 9c3b64c8f..5ca3b7686 100644 --- a/testassets/InteropTestsClient/Program.cs +++ b/testassets/InteropTestsClient/Program.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -33,51 +33,51 @@ public class Program public static async Task Main(string[] args) { var options = new List