From 5b89acede297d44b53e45a39a89dd39ddfa20899 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 23 Sep 2020 12:13:47 +1200 Subject: [PATCH] Support aggressive trimming --- Grpc.DotNet.sln | 14 ++ build/dependencies.props | 2 +- .../GrpcEndpointRouteBuilderExtensions.cs | 11 +- .../InterceptorCollection.cs | 13 +- .../InterceptorRegistration.cs | 14 +- .../Internal/DefaultGrpcServiceActivator.cs | 10 +- .../Internal/BinderServiceModelProvider.cs | 7 +- .../Model/Internal/ProviderServiceBinder.cs | 13 +- src/Shared/Server/BindMethodFinder.cs | 5 + .../Grpc.AspNetCore.FunctionalTests.csproj | 5 + .../Linker/Helpers/DotNetProcess.cs | 122 +++++++++++++++ .../Linker/Helpers/WebsiteProcess.cs | 70 +++++++++ test/FunctionalTests/Linker/LinkerTests.cs | 145 ++++++++++++++++++ testassets/Greeter/Greeter.sln | 67 ++++++++ testassets/Greeter/Proto/greet.proto | 33 ++++ .../LinkerTestsClient.csproj | 29 ++++ testassets/LinkerTestsClient/Program.cs | 78 ++++++++++ .../LinkerTestsWebsite.csproj | 30 ++++ testassets/LinkerTestsWebsite/Program.cs | 51 ++++++ .../Services/GreeterService.cs | 41 +++++ testassets/LinkerTestsWebsite/Startup.cs | 51 ++++++ 21 files changed, 803 insertions(+), 8 deletions(-) create mode 100644 test/FunctionalTests/Linker/Helpers/DotNetProcess.cs create mode 100644 test/FunctionalTests/Linker/Helpers/WebsiteProcess.cs create mode 100644 test/FunctionalTests/Linker/LinkerTests.cs create mode 100644 testassets/Greeter/Greeter.sln create mode 100644 testassets/Greeter/Proto/greet.proto create mode 100644 testassets/LinkerTestsClient/LinkerTestsClient.csproj create mode 100644 testassets/LinkerTestsClient/Program.cs create mode 100644 testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj create mode 100644 testassets/LinkerTestsWebsite/Program.cs create mode 100644 testassets/LinkerTestsWebsite/Services/GreeterService.cs create mode 100644 testassets/LinkerTestsWebsite/Startup.cs diff --git a/Grpc.DotNet.sln b/Grpc.DotNet.sln index 40ed008a7..1e829e4fc 100644 --- a/Grpc.DotNet.sln +++ b/Grpc.DotNet.sln @@ -118,6 +118,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTestsGrpcWebClient", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GrpcClient", "perf\benchmarkapps\GrpcClient\GrpcClient.csproj", "{D241B525-3B50-42EA-9E43-052745549BA6}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinkerTestsClient", "testassets\LinkerTestsClient\LinkerTestsClient.csproj", "{1FA0B07B-E65C-4380-94A4-75F10312487D}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "LinkerTestsWebsite", "testassets\LinkerTestsWebsite\LinkerTestsWebsite.csproj", "{FC5F9350-B812-4C62-AE76-0FF437F0F362}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -236,6 +240,14 @@ Global {D241B525-3B50-42EA-9E43-052745549BA6}.Debug|Any CPU.Build.0 = Debug|Any CPU {D241B525-3B50-42EA-9E43-052745549BA6}.Release|Any CPU.ActiveCfg = Release|Any CPU {D241B525-3B50-42EA-9E43-052745549BA6}.Release|Any CPU.Build.0 = Release|Any CPU + {1FA0B07B-E65C-4380-94A4-75F10312487D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1FA0B07B-E65C-4380-94A4-75F10312487D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1FA0B07B-E65C-4380-94A4-75F10312487D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1FA0B07B-E65C-4380-94A4-75F10312487D}.Release|Any CPU.Build.0 = Release|Any CPU + {FC5F9350-B812-4C62-AE76-0FF437F0F362}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FC5F9350-B812-4C62-AE76-0FF437F0F362}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FC5F9350-B812-4C62-AE76-0FF437F0F362}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FC5F9350-B812-4C62-AE76-0FF437F0F362}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -278,6 +290,8 @@ Global {AE2CF906-6C98-40F5-8EE5-3DBAC572F114} = {59C7B1F0-EE4D-4098-8596-0ADDBC305234} {10DD7EEB-7B3A-4BF2-9562-78831FB06001} = {59C7B1F0-EE4D-4098-8596-0ADDBC305234} {D241B525-3B50-42EA-9E43-052745549BA6} = {1B8B6117-CE39-4580-BAFA-D8026102767A} + {1FA0B07B-E65C-4380-94A4-75F10312487D} = {59C7B1F0-EE4D-4098-8596-0ADDBC305234} + {FC5F9350-B812-4C62-AE76-0FF437F0F362} = {59C7B1F0-EE4D-4098-8596-0ADDBC305234} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {CD5C2B19-49B4-480A-990C-36D98A719B07} diff --git a/build/dependencies.props b/build/dependencies.props index cf227c5a3..4b35fcb4c 100644 --- a/build/dependencies.props +++ b/build/dependencies.props @@ -3,7 +3,7 @@ 0.12.1 3.13.0 2.32.0 - 2.32.0 + 2.33.0-pre1 5.0.0-rc.1.20451.17 3.1.3 1.2.2 diff --git a/src/Grpc.AspNetCore.Server/GrpcEndpointRouteBuilderExtensions.cs b/src/Grpc.AspNetCore.Server/GrpcEndpointRouteBuilderExtensions.cs index 53b40041a..c7abfa06c 100644 --- a/src/Grpc.AspNetCore.Server/GrpcEndpointRouteBuilderExtensions.cs +++ b/src/Grpc.AspNetCore.Server/GrpcEndpointRouteBuilderExtensions.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.Diagnostics.CodeAnalysis; using Grpc.AspNetCore.Server.Internal; using Grpc.AspNetCore.Server.Model.Internal; using Microsoft.AspNetCore.Routing; @@ -29,13 +30,21 @@ namespace Microsoft.AspNetCore.Builder /// public static class GrpcEndpointRouteBuilderExtensions { +#if NET5_0 + private const DynamicallyAccessedMemberTypes ServiceAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; +#endif + /// /// Maps incoming requests to the specified type. /// /// The service type to map requests to. /// The to add the route to. /// A for endpoints associated with the service. - public static GrpcServiceEndpointConventionBuilder MapGrpcService(this IEndpointRouteBuilder builder) where TService : class + public static GrpcServiceEndpointConventionBuilder MapGrpcService< +#if NET5_0 + [DynamicallyAccessedMembers(ServiceAccessibility)] +#endif + TService>(this IEndpointRouteBuilder builder) where TService : class { if (builder == null) { diff --git a/src/Grpc.AspNetCore.Server/InterceptorCollection.cs b/src/Grpc.AspNetCore.Server/InterceptorCollection.cs index f204d63a3..f02472644 100644 --- a/src/Grpc.AspNetCore.Server/InterceptorCollection.cs +++ b/src/Grpc.AspNetCore.Server/InterceptorCollection.cs @@ -19,6 +19,7 @@ using System; using System.Collections.Generic; using System.Collections.ObjectModel; +using System.Diagnostics.CodeAnalysis; using Grpc.Core.Interceptors; namespace Grpc.AspNetCore.Server @@ -33,7 +34,11 @@ public class InterceptorCollection : Collection /// /// The interceptor type. /// The list of arguments to pass to the interceptor constructor when creating an instance. - public void Add(params object[] args) where TInterceptor : Interceptor + public void Add< +#if NET5_0 + [DynamicallyAccessedMembers(InterceptorRegistration.InterceptorAccessibility)] +#endif + TInterceptor>(params object[] args) where TInterceptor : Interceptor { Add(typeof(TInterceptor), args); } @@ -43,7 +48,11 @@ public void Add(params object[] args) where TInterceptor : Interce /// /// The interceptor type. /// The list of arguments to pass to the interceptor constructor when creating an instance. - public void Add(Type interceptorType, params object[] args) + public void Add( +#if NET5_0 + [DynamicallyAccessedMembers(InterceptorRegistration.InterceptorAccessibility)] +#endif + Type interceptorType, params object[] args) { if (interceptorType == null) { diff --git a/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs b/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs index a7ac67b4d..828c032a2 100644 --- a/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs +++ b/src/Grpc.AspNetCore.Server/InterceptorRegistration.cs @@ -18,6 +18,7 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Linq; using Grpc.AspNetCore.Server.Internal; using Microsoft.Extensions.DependencyInjection; @@ -29,9 +30,17 @@ namespace Grpc.AspNetCore.Server /// public class InterceptorRegistration { +#if NET5_0 + internal const DynamicallyAccessedMemberTypes InterceptorAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.PublicMethods; +#endif + internal object[] _args; - internal InterceptorRegistration(Type type, object[] arguments) + internal InterceptorRegistration( +#if NET5_0 + [DynamicallyAccessedMembers(InterceptorAccessibility)] +#endif + Type type, object[] arguments) { if (type == null) { @@ -56,6 +65,9 @@ internal InterceptorRegistration(Type type, object[] arguments) /// /// Get the type of the interceptor. /// +#if NET5_0 + [DynamicallyAccessedMembers(InterceptorAccessibility)] +#endif public Type Type { get; } /// diff --git a/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs b/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs index 453cd4236..af4dc2847 100644 --- a/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs +++ b/src/Grpc.AspNetCore.Server/Internal/DefaultGrpcServiceActivator.cs @@ -17,13 +17,21 @@ #endregion using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Microsoft.Extensions.DependencyInjection; namespace Grpc.AspNetCore.Server.Internal { - internal sealed class DefaultGrpcServiceActivator : IGrpcServiceActivator where TGrpcService : class + internal sealed class DefaultGrpcServiceActivator< +#if NET5_0 + [DynamicallyAccessedMembers(ServiceAccessibility)] +#endif + TGrpcService> : IGrpcServiceActivator where TGrpcService : class { +#if NET5_0 + internal const DynamicallyAccessedMemberTypes ServiceAccessibility = DynamicallyAccessedMemberTypes.PublicConstructors; +#endif private static readonly Lazy _objectFactory = new Lazy(() => ActivatorUtilities.CreateFactory(typeof(TGrpcService), Type.EmptyTypes)); public GrpcActivatorHandle Create(IServiceProvider serviceProvider) diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs b/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs index 1f057865b..de3b49945 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/BinderServiceModelProvider.cs @@ -17,12 +17,17 @@ #endregion using System; +using System.Diagnostics.CodeAnalysis; using Grpc.Shared.Server; using Microsoft.Extensions.Logging; namespace Grpc.AspNetCore.Server.Model.Internal { - internal class BinderServiceMethodProvider : IServiceMethodProvider where TService : class + internal class BinderServiceMethodProvider< +#if NET5_0 + [DynamicallyAccessedMembers(ProviderServiceBinder.ServiceAccessibility)] +#endif + TService> : IServiceMethodProvider where TService : class { private readonly ILogger> _logger; diff --git a/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs b/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs index 6dea91858..982c223f5 100644 --- a/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs +++ b/src/Grpc.AspNetCore.Server/Model/Internal/ProviderServiceBinder.cs @@ -18,14 +18,24 @@ using System; using System.Collections.Generic; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Grpc.Core; using Microsoft.AspNetCore.Routing; namespace Grpc.AspNetCore.Server.Model.Internal { - internal class ProviderServiceBinder : ServiceBinderBase where TService : class + internal class ProviderServiceBinder< +#if NET5_0 + [DynamicallyAccessedMembers(ServiceAccessibility)] +#endif + TService> : ServiceBinderBase where TService : class { +#if NET5_0 + // Non-public methods is required by GetMethod overload that has a BindingFlags argument. + internal const DynamicallyAccessedMemberTypes ServiceAccessibility = DynamicallyAccessedMemberTypes.PublicMethods | DynamicallyAccessedMemberTypes.NonPublicMethods; +#endif + private readonly ServiceMethodProviderContext _context; private readonly Type _declaringType; @@ -101,6 +111,7 @@ public override void AddMethod(Method Type? currentType = typeof(TService); while (currentType != null) { + // Specify binding flags explicitly because we don't want to match static methods. var matchingMethod = currentType.GetMethod( methodName, BindingFlags.Public | BindingFlags.Instance, diff --git a/src/Shared/Server/BindMethodFinder.cs b/src/Shared/Server/BindMethodFinder.cs index 2205d3492..28c36fe86 100644 --- a/src/Shared/Server/BindMethodFinder.cs +++ b/src/Shared/Server/BindMethodFinder.cs @@ -17,6 +17,7 @@ #endregion using System; +using System.Diagnostics.CodeAnalysis; using System.Reflection; using Grpc.Core; @@ -65,6 +66,10 @@ internal static class BindMethodFinder return null; } +#if NET5_0 + [UnconditionalSuppressMessage("ReflectionAnalysis", "IL2075:UnrecognizedReflectionPattern", + Justification = "Fallback doesn't have BindServiceMethodAttribute so can't be verified.")] +#endif internal static MethodInfo? GetBindMethodFallback(Type serviceType) { // Search for the generated service base class diff --git a/test/FunctionalTests/Grpc.AspNetCore.FunctionalTests.csproj b/test/FunctionalTests/Grpc.AspNetCore.FunctionalTests.csproj index f861e2da3..dab69c71f 100644 --- a/test/FunctionalTests/Grpc.AspNetCore.FunctionalTests.csproj +++ b/test/FunctionalTests/Grpc.AspNetCore.FunctionalTests.csproj @@ -29,6 +29,11 @@ PreserveNewest + + + <_Parameter1>ProjectDirectory + <_Parameter2>$(ProjectDir) + diff --git a/test/FunctionalTests/Linker/Helpers/DotNetProcess.cs b/test/FunctionalTests/Linker/Helpers/DotNetProcess.cs new file mode 100644 index 000000000..b878c86e8 --- /dev/null +++ b/test/FunctionalTests/Linker/Helpers/DotNetProcess.cs @@ -0,0 +1,122 @@ +#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 + +using System; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; + +namespace Grpc.AspNetCore.FunctionalTests.Linker.Helpers +{ + public class DotNetProcess : IDisposable + { + private readonly TaskCompletionSource _exitedTcs; + private readonly StringBuilder _output; + private readonly object _outputLock = new object(); + + protected Process Process { get; } + + public DotNetProcess() + { + _output = new StringBuilder(); + + Process = new Process(); + Process.StartInfo = new ProcessStartInfo + { + UseShellExecute = false, + CreateNoWindow = true, + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = @"dotnet" + }; + Process.EnableRaisingEvents = true; + Process.Exited += Process_Exited; + Process.OutputDataReceived += Process_OutputDataReceived; + Process.ErrorDataReceived += Process_ErrorDataReceived; + + _exitedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + public Task WaitForExitAsync() => _exitedTcs.Task; + public int ExitCode => Process.ExitCode; + public bool HasExited => Process.HasExited; + + public void Start(string arguments) + { + Process.StartInfo.Arguments = arguments; + Process.Start(); + + Process.BeginOutputReadLine(); + Process.BeginErrorReadLine(); + } + + public string GetOutput() + { + lock (_outputLock) + { + return _output.ToString(); + } + } + + private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + var data = e.Data; + if (data != null) + { + lock (_outputLock) + { + _output.AppendLine(data); + } + } + } + + private void Process_ErrorDataReceived(object sender, DataReceivedEventArgs e) + { + var data = e.Data; + if (data != null) + { + lock (_outputLock) + { + _output.AppendLine("ERROR: " + data); + } + } + } + + private void Process_Exited(object? sender, EventArgs e) + { + _exitedTcs.TrySetResult(null); + } + + public void Dispose() + { + try + { + if (!Process.HasExited) + { + Process.Kill(entireProcessTree: true); + } + } + catch + { + // Ignore error + } + + Process.Dispose(); + } + } +} diff --git a/test/FunctionalTests/Linker/Helpers/WebsiteProcess.cs b/test/FunctionalTests/Linker/Helpers/WebsiteProcess.cs new file mode 100644 index 000000000..ff9ad5226 --- /dev/null +++ b/test/FunctionalTests/Linker/Helpers/WebsiteProcess.cs @@ -0,0 +1,70 @@ +#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 + +using System; +using System.Diagnostics; +using System.Text.RegularExpressions; +using System.Threading.Tasks; + +namespace Grpc.AspNetCore.FunctionalTests.Linker.Helpers +{ + public class WebsiteProcess : DotNetProcess + { + private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: .*:(?\d*)$"); + + private readonly TaskCompletionSource _startTcs; + + public string? ServerPort { get; private set; } + public bool IsReady => _startTcs.Task.IsCompletedSuccessfully; + + public WebsiteProcess() + { + Process.OutputDataReceived += Process_OutputDataReceived; + + _startTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + public Task WaitForReadyAsync() + { + if (Process.HasExited) + { + return Task.FromException(new InvalidOperationException("Server is not running.")); + } + + return _startTcs.Task; + } + + private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + var data = e.Data; + if (data != null) + { + var m = NowListeningRegex.Match(data); + if (m.Success) + { + ServerPort = m.Groups["port"].Value; + } + + if (data.Contains("Application started. Press Ctrl+C to shut down.")) + { + _startTcs.TrySetResult(null); + } + } + } + } +} diff --git a/test/FunctionalTests/Linker/LinkerTests.cs b/test/FunctionalTests/Linker/LinkerTests.cs new file mode 100644 index 000000000..bcc5218e3 --- /dev/null +++ b/test/FunctionalTests/Linker/LinkerTests.cs @@ -0,0 +1,145 @@ +#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 + +using System; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Threading.Tasks; +using Grpc.AspNetCore.FunctionalTests.Linker.Helpers; +using Grpc.Tests.Shared; +using NUnit.Framework; + +namespace Grpc.AspNetCore.FunctionalTests.Linker +{ + [TestFixture] + [Category("LongRunning")] + public class LinkerTests + { + private static readonly TimeSpan Timeout = TimeSpan.FromSeconds(60); + + [Test] + public async Task RunWebsiteAndCallWithClient_Success() + { + var projectDirectory = typeof(LinkerTests).Assembly + .GetCustomAttributes() + .Single(a => a.Key == "ProjectDirectory") + .Value; + + var tempPath = Path.GetTempPath(); + var linkerTestsClientPath = Path.Combine(tempPath, "LinkerTestsClient"); + var linkerTestsWebsitePath = Path.Combine(tempPath, "LinkerTestsWebsite"); + + EnsureDeleted(linkerTestsClientPath); + EnsureDeleted(linkerTestsWebsitePath); + + try + { + using var websiteProcess = new WebsiteProcess(); + using var clientProcess = new DotNetProcess(); + + try + { + await PublishApp(projectDirectory + @"\..\..\testassets\LinkerTestsWebsite\LinkerTestsWebsite.csproj", linkerTestsWebsitePath) + .TimeoutAfter(Timeout); + await PublishApp(projectDirectory + @"\..\..\testassets\LinkerTestsClient\LinkerTestsClient.csproj", linkerTestsClientPath) + .TimeoutAfter(Timeout); + + websiteProcess.Start(Path.Combine(linkerTestsWebsitePath, "LinkerTestsWebsite.dll")); + await websiteProcess.WaitForReadyAsync().TimeoutAfter(Timeout); + + clientProcess.Start(Path.Combine(linkerTestsClientPath, $"LinkerTestsClient.dll {websiteProcess.ServerPort}")); + await clientProcess.WaitForExitAsync().TimeoutAfter(Timeout); + + Assert.AreEqual(0, clientProcess.ExitCode); + } + finally + { + Console.WriteLine("Website output:"); + Console.WriteLine(websiteProcess.GetOutput()); + Console.WriteLine("Client output:"); + Console.WriteLine(clientProcess.GetOutput()); + } + } + finally + { + EnsureDeleted(linkerTestsClientPath); + EnsureDeleted(linkerTestsWebsitePath); + } + } + + private void EnsureDeleted(string path) + { + if (Directory.Exists(path)) + { + Directory.Delete(path, recursive: true); + } + } + + private static async Task PublishApp(string path, string outputPath) + { + var resolvedPath = Path.GetFullPath(path); + Console.WriteLine($"Publishing {resolvedPath}"); + + var process = new DotNetProcess(); + try + { + process.Start($"publish {resolvedPath} -r {GetRuntimeIdentifier()} -c Release -o {outputPath}"); + await process.WaitForExitAsync().TimeoutAfter(Timeout); + } + catch (Exception ex) + { + throw new Exception("Error while publishing app.", ex); + } + finally + { + var exitCode = process.HasExited ? (int?)process.ExitCode : null; + + process.Dispose(); + + var output = process.GetOutput(); + Console.WriteLine("Publish output:"); + Console.WriteLine(output); + + if (exitCode != null && exitCode.Value != 0) + { + throw new Exception($"Non-zero exit code returned: {exitCode}"); + } + } + } + + private static string GetRuntimeIdentifier() + { + var architecture = RuntimeInformation.OSArchitecture.ToString().ToLower(); + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + return "win10-" + architecture; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + return "linux-" + architecture; + } + if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + return "osx-" + architecture; + } + throw new InvalidOperationException("Unrecognized operation system platform"); + } + } +} diff --git a/testassets/Greeter/Greeter.sln b/testassets/Greeter/Greeter.sln new file mode 100644 index 000000000..c76dca87d --- /dev/null +++ b/testassets/Greeter/Greeter.sln @@ -0,0 +1,67 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29230.61 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Server", "Server\Server.csproj", "{534AC5F8-2DF2-40BD-87A5-B3D8310118C4}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Client", "Client\Client.csproj", "{48A1D3BC-A14B-436A-8822-6DE2BEF8B747}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.Common", "..\..\src\Grpc.Net.Common\Grpc.Net.Common.csproj", "{7BAC4C72-8BE1-4642-9E16-E37D23B9F386}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.AspNetCore", "..\..\src\Grpc.AspNetCore\Grpc.AspNetCore.csproj", "{F1713F9E-6A06-4ADC-99C7-88A8F9C80351}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.ClientFactory", "..\..\src\Grpc.Net.ClientFactory\Grpc.Net.ClientFactory.csproj", "{4BDD8F78-41C4-4023-9680-2A368DBA9691}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.Net.Client", "..\..\src\Grpc.Net.Client\Grpc.Net.Client.csproj", "{55CB8BDE-D0BB-455E-8A2C-7659CFBDC2D3}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.AspNetCore.Server.ClientFactory", "..\..\src\Grpc.AspNetCore.Server.ClientFactory\Grpc.AspNetCore.Server.ClientFactory.csproj", "{DF94E310-94BA-4C92-BD08-A2DA5E6D32A2}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Grpc.AspNetCore.Server", "..\..\src\Grpc.AspNetCore.Server\Grpc.AspNetCore.Server.csproj", "{98BE0416-3B7D-4F88-A5B6-0F7349843215}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {534AC5F8-2DF2-40BD-87A5-B3D8310118C4}.Release|Any CPU.Build.0 = Release|Any CPU + {48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Debug|Any CPU.Build.0 = Debug|Any CPU + {48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Release|Any CPU.ActiveCfg = Release|Any CPU + {48A1D3BC-A14B-436A-8822-6DE2BEF8B747}.Release|Any CPU.Build.0 = Release|Any CPU + {7BAC4C72-8BE1-4642-9E16-E37D23B9F386}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7BAC4C72-8BE1-4642-9E16-E37D23B9F386}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7BAC4C72-8BE1-4642-9E16-E37D23B9F386}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7BAC4C72-8BE1-4642-9E16-E37D23B9F386}.Release|Any CPU.Build.0 = Release|Any CPU + {F1713F9E-6A06-4ADC-99C7-88A8F9C80351}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F1713F9E-6A06-4ADC-99C7-88A8F9C80351}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F1713F9E-6A06-4ADC-99C7-88A8F9C80351}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F1713F9E-6A06-4ADC-99C7-88A8F9C80351}.Release|Any CPU.Build.0 = Release|Any CPU + {4BDD8F78-41C4-4023-9680-2A368DBA9691}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4BDD8F78-41C4-4023-9680-2A368DBA9691}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4BDD8F78-41C4-4023-9680-2A368DBA9691}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4BDD8F78-41C4-4023-9680-2A368DBA9691}.Release|Any CPU.Build.0 = Release|Any CPU + {55CB8BDE-D0BB-455E-8A2C-7659CFBDC2D3}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {55CB8BDE-D0BB-455E-8A2C-7659CFBDC2D3}.Debug|Any CPU.Build.0 = Debug|Any CPU + {55CB8BDE-D0BB-455E-8A2C-7659CFBDC2D3}.Release|Any CPU.ActiveCfg = Release|Any CPU + {55CB8BDE-D0BB-455E-8A2C-7659CFBDC2D3}.Release|Any CPU.Build.0 = Release|Any CPU + {DF94E310-94BA-4C92-BD08-A2DA5E6D32A2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DF94E310-94BA-4C92-BD08-A2DA5E6D32A2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DF94E310-94BA-4C92-BD08-A2DA5E6D32A2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {DF94E310-94BA-4C92-BD08-A2DA5E6D32A2}.Release|Any CPU.Build.0 = Release|Any CPU + {98BE0416-3B7D-4F88-A5B6-0F7349843215}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {98BE0416-3B7D-4F88-A5B6-0F7349843215}.Debug|Any CPU.Build.0 = Debug|Any CPU + {98BE0416-3B7D-4F88-A5B6-0F7349843215}.Release|Any CPU.ActiveCfg = Release|Any CPU + {98BE0416-3B7D-4F88-A5B6-0F7349843215}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D22B3129-3BFB-41FA-9FCE-E45EBEF8C2DD} + EndGlobalSection +EndGlobal diff --git a/testassets/Greeter/Proto/greet.proto b/testassets/Greeter/Proto/greet.proto new file mode 100644 index 000000000..26d0c794d --- /dev/null +++ b/testassets/Greeter/Proto/greet.proto @@ -0,0 +1,33 @@ +// 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. + +syntax = "proto3"; + +package greet; + +// The greeting service definition. +service Greeter { + // Sends a greeting + rpc SayHello (HelloRequest) returns (HelloReply); +} + +// The request message containing the user's name. +message HelloRequest { + string name = 1; +} + +// The response message containing the greetings +message HelloReply { + string message = 1; +} diff --git a/testassets/LinkerTestsClient/LinkerTestsClient.csproj b/testassets/LinkerTestsClient/LinkerTestsClient.csproj new file mode 100644 index 000000000..983a04fcb --- /dev/null +++ b/testassets/LinkerTestsClient/LinkerTestsClient.csproj @@ -0,0 +1,29 @@ + + + + net5.0 + Exe + + true + Link + + + + + + + + + + + + + + + link + + + + + + diff --git a/testassets/LinkerTestsClient/Program.cs b/testassets/LinkerTestsClient/Program.cs new file mode 100644 index 000000000..f0941ba0c --- /dev/null +++ b/testassets/LinkerTestsClient/Program.cs @@ -0,0 +1,78 @@ +#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 + +using System; +using System.Threading.Tasks; +using Greet; +using Grpc.Core; +using Grpc.Net.Client; +using Unimplemented; + +namespace Client +{ + public class Program + { + static async Task Main(string[] args) + { + try + { + if (args.Length != 1 || !int.TryParse(args[0], out var port)) + { + throw new Exception("Port must be passed as an argument."); + } + + using var channel = GrpcChannel.ForAddress($"http://localhost:{port}"); + await CallGreeter(channel); + await CallUnimplemented(channel); + + Console.WriteLine("Shutting down"); + return 0; + } + catch (Exception ex) + { + Console.Error.WriteLine(ex.ToString()); + return 1; + } + } + + private static async Task CallGreeter(GrpcChannel channel) + { + var client = new Greeter.GreeterClient(channel); + + var reply = await client.SayHelloAsync(new HelloRequest { Name = "GreeterClient" }); + Console.WriteLine("Greeting: " + reply.Message); + } + + private static async Task CallUnimplemented(GrpcChannel channel) + { + var client = new UnimplementedService.UnimplementedServiceClient(channel); + + var reply = client.DuplexData(); + + try + { + await reply.ResponseStream.MoveNext(); + throw new Exception("Expected error status."); + } + catch (RpcException ex) when (ex.StatusCode == StatusCode.Unimplemented) + { + Console.WriteLine("Unimplemented status correctly returned."); + } + } + } +} diff --git a/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj b/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj new file mode 100644 index 000000000..b625cfe59 --- /dev/null +++ b/testassets/LinkerTestsWebsite/LinkerTestsWebsite.csproj @@ -0,0 +1,30 @@ + + + + net5.0 + Exe + + true + Link + + + + + + + + + + + + + + + + link + + + + + + diff --git a/testassets/LinkerTestsWebsite/Program.cs b/testassets/LinkerTestsWebsite/Program.cs new file mode 100644 index 000000000..a5c0db931 --- /dev/null +++ b/testassets/LinkerTestsWebsite/Program.cs @@ -0,0 +1,51 @@ +#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 + +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Hosting; +using Microsoft.Extensions.Logging; + +namespace Server +{ + public class Program + { + public static void Main(string[] args) + { + CreateHostBuilder(args).Build().Run(); + } + + public static IHostBuilder CreateHostBuilder(string[] args) => + Host.CreateDefaultBuilder(args) + .ConfigureWebHostDefaults(webBuilder => + { + webBuilder.UseStartup(); + webBuilder.ConfigureKestrel(options => + { + options.ListenAnyIP(0, listenOptions => + { + listenOptions.Protocols = HttpProtocols.Http2; + }); + }); + }) + .ConfigureLogging(logging => + { + logging.SetMinimumLevel(LogLevel.Trace); + }); + } +} diff --git a/testassets/LinkerTestsWebsite/Services/GreeterService.cs b/testassets/LinkerTestsWebsite/Services/GreeterService.cs new file mode 100644 index 000000000..1ca09856d --- /dev/null +++ b/testassets/LinkerTestsWebsite/Services/GreeterService.cs @@ -0,0 +1,41 @@ +#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 + +using System.Threading.Tasks; +using Greet; +using Grpc.Core; +using Microsoft.Extensions.Logging; + +namespace Server +{ + public class GreeterService : Greeter.GreeterBase + { + private readonly ILogger _logger; + + public GreeterService(ILoggerFactory loggerFactory) + { + _logger = loggerFactory.CreateLogger(); + } + + public override Task SayHello(HelloRequest request, ServerCallContext context) + { + _logger.LogInformation($"Sending hello to {request.Name}"); + return Task.FromResult(new HelloReply { Message = "Hello " + request.Name }); + } + } +} diff --git a/testassets/LinkerTestsWebsite/Startup.cs b/testassets/LinkerTestsWebsite/Startup.cs new file mode 100644 index 000000000..c089aff9f --- /dev/null +++ b/testassets/LinkerTestsWebsite/Startup.cs @@ -0,0 +1,51 @@ +#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 + +using Grpc.AspNetCore.Server; +using Microsoft.AspNetCore.Builder; +using Microsoft.AspNetCore.Hosting; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace Server +{ + public class Startup + { + public void ConfigureServices(IServiceCollection services) + { + typeof(GrpcServiceOptions).GetMethod("name"); + + services.AddGrpc(); + } + + public void Configure(IApplicationBuilder app, IWebHostEnvironment env) + { + if (env.IsDevelopment()) + { + app.UseDeveloperExceptionPage(); + } + + app.UseRouting(); + + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + }); + } + } +}