From 0b7f2d03c64b0c105dacd8288de5086ac0384ed0 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 11:07:07 -0800 Subject: [PATCH 01/29] WIP add interop tests --- src/Grpc/Directory.Build.props | 2 + src/Grpc/Directory.Build.targets | 2 + src/Grpc/Grpc.sln | 46 + src/Grpc/startvs.cmd | 3 + src/Grpc/test/InteropTests/ClientProcess.cs | 60 ++ src/Grpc/test/InteropTests/InteropTests.cs | 67 ++ .../test/InteropTests/InteropTests.csproj | 19 + .../test/InteropTests/InteropTestsFixture.cs | 31 + src/Grpc/test/InteropTests/TaskExtensions.cs | 66 ++ .../test/InteropTests/WebServerProcess.cs | 68 ++ .../testassets/InteropTestsClient/Assert.cs | 131 +++ .../AsyncStreamExtensions.cs | 86 ++ .../InteropTestsClient/IChannelWrapper.cs | 46 + .../InteropTestsClient/InteropClient.cs | 899 ++++++++++++++++++ .../InteropTestsClient.csproj | 25 + .../testassets/InteropTestsClient/Program.cs | 32 + .../InteropTestsClient/RunTests.ps1 | 53 ++ .../AsyncStreamExtensions.cs | 41 + .../InteropTestsWebsite.csproj | 19 + .../testassets/InteropTestsWebsite/Program.cs | 65 ++ .../testassets/InteropTestsWebsite/README.md | 27 + .../testassets/InteropTestsWebsite/Startup.cs | 49 + .../InteropTestsWebsite/TestServiceImpl.cs | 149 +++ .../testassets/Proto/grpc/testing/empty.proto | 28 + .../Proto/grpc/testing/messages.proto | 165 ++++ .../testassets/Proto/grpc/testing/test.proto | 79 ++ src/Shared/Process/ProcessEx.cs | 210 ++++ 27 files changed, 2468 insertions(+) create mode 100644 src/Grpc/Directory.Build.props create mode 100644 src/Grpc/Directory.Build.targets create mode 100644 src/Grpc/Grpc.sln create mode 100644 src/Grpc/startvs.cmd create mode 100644 src/Grpc/test/InteropTests/ClientProcess.cs create mode 100644 src/Grpc/test/InteropTests/InteropTests.cs create mode 100644 src/Grpc/test/InteropTests/InteropTests.csproj create mode 100644 src/Grpc/test/InteropTests/InteropTestsFixture.cs create mode 100644 src/Grpc/test/InteropTests/TaskExtensions.cs create mode 100644 src/Grpc/test/InteropTests/WebServerProcess.cs create mode 100644 src/Grpc/test/testassets/InteropTestsClient/Assert.cs create mode 100644 src/Grpc/test/testassets/InteropTestsClient/AsyncStreamExtensions.cs create mode 100644 src/Grpc/test/testassets/InteropTestsClient/IChannelWrapper.cs create mode 100644 src/Grpc/test/testassets/InteropTestsClient/InteropClient.cs create mode 100644 src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj create mode 100644 src/Grpc/test/testassets/InteropTestsClient/Program.cs create mode 100644 src/Grpc/test/testassets/InteropTestsClient/RunTests.ps1 create mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/AsyncStreamExtensions.cs create mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj create mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/Program.cs create mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/README.md create mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/Startup.cs create mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/TestServiceImpl.cs create mode 100644 src/Grpc/test/testassets/Proto/grpc/testing/empty.proto create mode 100644 src/Grpc/test/testassets/Proto/grpc/testing/messages.proto create mode 100644 src/Grpc/test/testassets/Proto/grpc/testing/test.proto create mode 100644 src/Shared/Process/ProcessEx.cs diff --git a/src/Grpc/Directory.Build.props b/src/Grpc/Directory.Build.props new file mode 100644 index 000000000000..8c119d5413b5 --- /dev/null +++ b/src/Grpc/Directory.Build.props @@ -0,0 +1,2 @@ + + diff --git a/src/Grpc/Directory.Build.targets b/src/Grpc/Directory.Build.targets new file mode 100644 index 000000000000..8c119d5413b5 --- /dev/null +++ b/src/Grpc/Directory.Build.targets @@ -0,0 +1,2 @@ + + diff --git a/src/Grpc/Grpc.sln b/src/Grpc/Grpc.sln new file mode 100644 index 000000000000..25211ccd538b --- /dev/null +++ b/src/Grpc/Grpc.sln @@ -0,0 +1,46 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.29505.145 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0FFB3605-0203-450F-80C8-F82CA2E8269F}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{F5841B0A-901A-448F-9CC5-4CB393CE86AF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTestsWebsite", "test\testassets\InteropTestsWebsite\InteropTestsWebsite.csproj", "{CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTestsClient", "test\testassets\InteropTestsClient\InteropTestsClient.csproj", "{285945D7-DF5A-456B-920A-AFA459D6A1B2}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropTests", "test\InteropTests\InteropTests.csproj", "{90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Debug|Any CPU.Build.0 = Debug|Any CPU + {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Release|Any CPU.ActiveCfg = Release|Any CPU + {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Release|Any CPU.Build.0 = Release|Any CPU + {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Release|Any CPU.Build.0 = Release|Any CPU + {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU + {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU + {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43} = {F5841B0A-901A-448F-9CC5-4CB393CE86AF} + {285945D7-DF5A-456B-920A-AFA459D6A1B2} = {F5841B0A-901A-448F-9CC5-4CB393CE86AF} + {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2} = {0FFB3605-0203-450F-80C8-F82CA2E8269F} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {3CAE66FD-9A59-49C2-B133-1D599225259A} + EndGlobalSection +EndGlobal diff --git a/src/Grpc/startvs.cmd b/src/Grpc/startvs.cmd new file mode 100644 index 000000000000..a4a22788e6f8 --- /dev/null +++ b/src/Grpc/startvs.cmd @@ -0,0 +1,3 @@ +@ECHO OFF + +%~dp0..\..\startvs.cmd %~dp0Grpc.sln diff --git a/src/Grpc/test/InteropTests/ClientProcess.cs b/src/Grpc/test/InteropTests/ClientProcess.cs new file mode 100644 index 000000000000..72fbdc5d1953 --- /dev/null +++ b/src/Grpc/test/InteropTests/ClientProcess.cs @@ -0,0 +1,60 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; +using Xunit.Abstractions; + +namespace InteropTests +{ + public class ClientProcess : IDisposable + { + private readonly Process _process; + private readonly ProcessEx _processEx; + private readonly TaskCompletionSource _startTcs; + + public ClientProcess(ITestOutputHelper output, string path, int port, string testCase) + { + _process = new Process(); + _process.StartInfo = new ProcessStartInfo + { + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = "dotnet.exe", + Arguments = @$"run -p {path} --use_tls false --server_port {port} --client_type httpclient --test_case {testCase}" + }; + _process.EnableRaisingEvents = true; + _process.OutputDataReceived += Process_OutputDataReceived; + _process.Start(); + + _processEx = new ProcessEx(output, _process); + + _startTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + public Task WaitForReady() + { + return _startTcs.Task; + } + + public int ExitCode => _process.ExitCode; + public Task Exited => _processEx.Exited; + + private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + var data = e.Data; + if (data != null) + { + if (data.Contains("Application started.")) + { + _startTcs.TrySetResult(null); + } + } + } + + public void Dispose() + { + _processEx.Dispose(); + } + } +} diff --git a/src/Grpc/test/InteropTests/InteropTests.cs b/src/Grpc/test/InteropTests/InteropTests.cs new file mode 100644 index 000000000000..f271c394b062 --- /dev/null +++ b/src/Grpc/test/InteropTests/InteropTests.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Threading.Tasks; +using Xunit; +using Xunit.Abstractions; + +namespace InteropTests +{ + public class InteropTests : IClassFixture + { + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); + + // All interop test cases, minus GCE authentication specific tests + private static string[] AllTests = new string[] + { + "empty_unary", + "large_unary", + "client_streaming", + "server_streaming", + "ping_pong", + "empty_stream", + + "cancel_after_begin", + "cancel_after_first_response", + "timeout_on_sleeping_server", + "custom_metadata", + "status_code_and_message", + "special_status_message", + "unimplemented_service", + "unimplemented_method", + "client_compressed_unary", + "client_compressed_streaming", + "server_compressed_unary", + "server_compressed_streaming" + }; + + public static IEnumerable TestCaseData => AllTests.Select(t => new object[] { t }); + + private readonly ITestOutputHelper _output; + private readonly InteropTestsFixture _fixture; + + public InteropTests(ITestOutputHelper output, InteropTestsFixture fixture) + { + _output = output; + _fixture = fixture; + } + + [Theory] + [MemberData(nameof(TestCaseData))] + public async Task InteropTestCase(string name) + { + await _fixture.EnsureStarted(_output); + + var clientPath = @"C:\Development\Source\AspNetCore\src\Grpc\test\testassets\InteropTestsClient\"; + + using (var clientProcess = new ClientProcess(_output, clientPath, 50052, name)) + { + await clientProcess.WaitForReady().TimeoutAfter(DefaultTimeout); + + await clientProcess.Exited.TimeoutAfter(DefaultTimeout); + + Assert.Equal(0, clientProcess.ExitCode); + } + } + } +} diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj new file mode 100644 index 000000000000..b4acf820c565 --- /dev/null +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp5.0 + + + + + + + + + + + + + + + diff --git a/src/Grpc/test/InteropTests/InteropTestsFixture.cs b/src/Grpc/test/InteropTests/InteropTestsFixture.cs new file mode 100644 index 000000000000..97ad333799a6 --- /dev/null +++ b/src/Grpc/test/InteropTests/InteropTestsFixture.cs @@ -0,0 +1,31 @@ +using System; +using System.Threading.Tasks; +using Xunit.Abstractions; + +namespace InteropTests +{ + public class InteropTestsFixture : IDisposable + { + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); + private WebServerProcess _process; + + public async Task EnsureStarted(ITestOutputHelper output) + { + if (_process != null) + { + return; + } + + var webPath = @"C:\Development\Source\AspNetCore\src\Grpc\test\testassets\InteropTestsWebsite\"; + + _process = new WebServerProcess(webPath, output); + + await _process.WaitForReady().TimeoutAfter(DefaultTimeout); + } + + public void Dispose() + { + _process.Dispose(); + } + } +} diff --git a/src/Grpc/test/InteropTests/TaskExtensions.cs b/src/Grpc/test/InteropTests/TaskExtensions.cs new file mode 100644 index 000000000000..da0ac5c00129 --- /dev/null +++ b/src/Grpc/test/InteropTests/TaskExtensions.cs @@ -0,0 +1,66 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Runtime.CompilerServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; + +namespace InteropTests +{ + public static class TaskExtensions + { + public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default) + { + // Don't create a timer if the task is already completed + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) + { + return await task; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + return await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + public static async Task TimeoutAfter(this Task task, TimeSpan timeout, + [CallerFilePath] string filePath = null, + [CallerLineNumber] int lineNumber = default) + { + // Don't create a timer if the task is already completed + // or the debugger is attached + if (task.IsCompleted || Debugger.IsAttached) + { + await task; + return; + } + + var cts = new CancellationTokenSource(); + if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) + { + cts.Cancel(); + await task; + } + else + { + throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); + } + } + + private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber) + => string.IsNullOrEmpty(filePath) + ? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms." + : $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms."; + } +} diff --git a/src/Grpc/test/InteropTests/WebServerProcess.cs b/src/Grpc/test/InteropTests/WebServerProcess.cs new file mode 100644 index 000000000000..5a07b9c726f7 --- /dev/null +++ b/src/Grpc/test/InteropTests/WebServerProcess.cs @@ -0,0 +1,68 @@ +using System; +using System.Diagnostics; +using System.Text; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; +using Xunit.Abstractions; + +namespace InteropTests +{ + public class WebServerProcess : IDisposable + { + private readonly Process _process; + private readonly ProcessEx _processEx; + private readonly TaskCompletionSource _startTcs; + private readonly StringBuilder _output; + + public WebServerProcess(string path, ITestOutputHelper output) + { + _process = new Process(); + _process.StartInfo = new ProcessStartInfo + { + RedirectStandardOutput = true, + RedirectStandardError = true, + FileName = "dotnet.exe", + Arguments = @$"run -p {path}" + }; + _process.EnableRaisingEvents = true; + _process.OutputDataReceived += Process_OutputDataReceived; + _process.Start(); + + _processEx = new ProcessEx(output, _process); + + _startTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + _output = new StringBuilder(); + } + + public Task WaitForReady() + { + if (_processEx.HasExited) + { + return Task.FromException(new InvalidOperationException("Server is not running.")); + } + + return _startTcs.Task; + } + + public Task Exited => _processEx.Exited; + + private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) + { + var data = e.Data; + if (data != null) + { + _output.AppendLine(data); + + if (data.Contains("Application started.")) + { + _startTcs.TrySetResult(null); + } + } + } + + public void Dispose() + { + _processEx.Dispose(); + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsClient/Assert.cs b/src/Grpc/test/testassets/InteropTestsClient/Assert.cs new file mode 100644 index 000000000000..8ae666f41166 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/Assert.cs @@ -0,0 +1,131 @@ +#region Copyright notice and license + +// Copyright 2015-2016 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.Collections; +using System.Threading.Tasks; + +namespace InteropTestsClient +{ + internal static class Assert + { + public static void IsTrue(bool condition) + { + if (!condition) + { + throw new InvalidOperationException("Expected true but got false."); + } + } + + public static void IsFalse(bool condition) + { + if (condition) + { + throw new InvalidOperationException("Expected false but got true."); + } + } + + public static void AreEqual(object expected, object actual) + { + if (!Equals(expected, actual)) + { + throw new InvalidOperationException($"Expected {expected} but got {actual}."); + } + } + + public static void IsNotNull(object value) + { + if (value == null) + { + throw new InvalidOperationException("Expected not null but got null."); + } + } + + public static void Fail() + { + throw new InvalidOperationException("Failure assert."); + } + + public static async Task ThrowsAsync(Func action) where TException : Exception + { + try + { + await action(); + } + catch (Exception ex) + { + if (ex.GetType() == typeof(TException)) + { + return (TException)ex; + } + + throw new InvalidOperationException($"Expected ${typeof(TException)} but got ${ex.GetType()}."); + } + + throw new InvalidOperationException("No exception thrown."); + } + + public static TException Throws(Action action) where TException : Exception + { + try + { + action(); + } + catch (Exception ex) + { + if (ex.GetType() == typeof(TException)) + { + return (TException)ex; + } + + throw new InvalidOperationException($"Expected ${typeof(TException)} but got ${ex.GetType()}."); + } + + throw new InvalidOperationException("No exception thrown."); + } + + public static void Contains(object expected, ICollection actual) + { + foreach (var item in actual) + { + if (Equals(item, expected)) + { + return; + } + } + + throw new InvalidOperationException($"Could not find {expected} in the collection."); + } + } + + internal static class CollectionAssert + { + public static void AreEqual(IList expected, IList actual) + { + if (expected.Count != actual.Count) + { + throw new InvalidOperationException($"Collection lengths differ. {expected.Count} but got {actual.Count}."); + } + + for (var i = 0; i < expected.Count; i++) + { + Assert.AreEqual(expected[i]!, actual[i]!); + } + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsClient/AsyncStreamExtensions.cs b/src/Grpc/test/testassets/InteropTestsClient/AsyncStreamExtensions.cs new file mode 100644 index 000000000000..00c1876fd13a --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/AsyncStreamExtensions.cs @@ -0,0 +1,86 @@ +#region Copyright notice and license + +// Copyright 2015 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.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; + +namespace InteropTestsClient +{ + /// + /// Extension methods that simplify work with gRPC streaming calls. + /// + public static class AsyncStreamExtensions + { + /// + /// Reads the entire stream and executes an async action for each element. + /// + public static async Task ForEachAsync(this IAsyncStreamReader streamReader, Func asyncAction) + where T : class + { + while (await streamReader.MoveNext().ConfigureAwait(false)) + { + await asyncAction(streamReader.Current).ConfigureAwait(false); + } + } + + /// + /// Reads the entire stream and creates a list containing all the elements read. + /// + public static async Task> ToListAsync(this IAsyncStreamReader streamReader) + where T : class + { + var result = new List(); + while (await streamReader.MoveNext().ConfigureAwait(false)) + { + result.Add(streamReader.Current); + } + return result; + } + + /// + /// Writes all elements from given enumerable to the stream. + /// Completes the stream afterwards unless close = false. + /// + public static async Task WriteAllAsync(this IClientStreamWriter streamWriter, IEnumerable elements, bool complete = true) + where T : class + { + foreach (var element in elements) + { + await streamWriter.WriteAsync(element).ConfigureAwait(false); + } + if (complete) + { + await streamWriter.CompleteAsync().ConfigureAwait(false); + } + } + + /// + /// Writes all elements from given enumerable to the stream. + /// + public static async Task WriteAllAsync(this IServerStreamWriter streamWriter, IEnumerable elements) + where T : class + { + foreach (var element in elements) + { + await streamWriter.WriteAsync(element).ConfigureAwait(false); + } + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsClient/IChannelWrapper.cs b/src/Grpc/test/testassets/InteropTestsClient/IChannelWrapper.cs new file mode 100644 index 000000000000..4cbc82532b52 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/IChannelWrapper.cs @@ -0,0 +1,46 @@ +#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.Net.Http; +using System.Threading.Tasks; +using Grpc.Core; +using Grpc.Net.Client; + +namespace InteropTestsClient +{ + public interface IChannelWrapper + { + ChannelBase Channel { get; } + Task ShutdownAsync(); + } + + public class GrpcChannelWrapper : IChannelWrapper + { + public ChannelBase Channel { get; } + + public GrpcChannelWrapper(GrpcChannel channel) + { + Channel = channel; + } + + public Task ShutdownAsync() + { + return Task.CompletedTask; + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsClient/InteropClient.cs b/src/Grpc/test/testassets/InteropTestsClient/InteropClient.cs new file mode 100644 index 000000000000..5c77b621d8e9 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/InteropClient.cs @@ -0,0 +1,899 @@ +#region Copyright notice and license + +// Copyright 2015-2016 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.Collections.Generic; +using System.IO; +using System.Linq; +using System.Net.Http; +using System.Security.Cryptography.X509Certificates; +using System.Threading; +using System.Threading.Tasks; +using CommandLine; +using Google.Apis.Auth.OAuth2; +using Google.Protobuf; +using Grpc.Auth; +using Grpc.Core; +using Grpc.Core.Utils; +using Grpc.Net.Client; +using Grpc.Testing; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json.Linq; + +namespace InteropTestsClient +{ + public class InteropClient : IDisposable + { + internal const string CompressionRequestAlgorithmMetadataKey = "grpc-internal-encoding-request"; + + private class ClientOptions + { + [Option("client_type", Default = "httpclient")] + public string? ClientType { get; set; } + + [Option("server_host", Default = "localhost")] + public string? ServerHost { get; set; } + + [Option("server_host_override")] + public string? ServerHostOverride { get; set; } + + [Option("server_port" +#if DEBUG + , Default = 50052 +#endif + )] + public int ServerPort { get; set; } + + [Option("test_case" +#if DEBUG + , Default = "large_unary" +#endif + )] + public string? TestCase { get; set; } + + // Deliberately using nullable bool type to allow --use_tls=true syntax (as opposed to --use_tls) + [Option("use_tls", Default = false)] + public bool? UseTls { get; set; } + + // Deliberately using nullable bool type to allow --use_test_ca=true syntax (as opposed to --use_test_ca) + [Option("use_test_ca", Default = false)] + public bool? UseTestCa { get; set; } + + [Option("default_service_account", Required = false)] + public string? DefaultServiceAccount { get; set; } + + [Option("oauth_scope", Required = false)] + public string? OAuthScope { get; set; } + + [Option("service_account_key_file", Required = false)] + public string? ServiceAccountKeyFile { get; set; } + } + + private ServiceProvider serviceProvider; + private ILoggerFactory loggerFactory; + private ClientOptions options; + + private InteropClient(ClientOptions options) + { + this.options = options; + + var services = new ServiceCollection(); + services.AddLogging(configure => + { + configure.SetMinimumLevel(Microsoft.Extensions.Logging.LogLevel.Trace); + configure.AddConsole(loggerOptions => loggerOptions.IncludeScopes = true); + }); + + serviceProvider = services.BuildServiceProvider(); + + loggerFactory = serviceProvider.GetRequiredService(); + } + + public void Dispose() + { + serviceProvider.Dispose(); + } + + public static void Run(string[] args) + { + var parserResult = Parser.Default.ParseArguments(args) + .WithNotParsed(errors => Environment.Exit(1)) + .WithParsed(options => + { + Console.WriteLine("Use TLS: " + options.UseTls); + Console.WriteLine("Use Test CA: " + options.UseTestCa); + Console.WriteLine("Client type: " + options.ClientType); + Console.WriteLine("Server host: " + options.ServerHost); + Console.WriteLine("Server port: " + options.ServerPort); + + using (var interopClient = new InteropClient(options)) + { + interopClient.Run().Wait(); + } + }); + } + + private async Task Run() + { + var channel = await HttpClientCreateChannel(); + await RunTestCaseAsync(channel, options); + await channel.ShutdownAsync(); + } + + private async Task HttpClientCreateChannel() + { + var credentials = await CreateCredentialsAsync(useTestCaOverride: false); + + string scheme; + if (!(options.UseTls ?? false)) + { + AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); + scheme = "http"; + } + else + { + scheme = "https"; + } + + var httpClientHandler = new HttpClientHandler(); + httpClientHandler.ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator; + + if (options.UseTestCa ?? false) + { + var pem = File.ReadAllText("Certs/ca.pem"); + var certData = GetBytesFromPem(pem, "CERTIFICATE"); + var cert = new X509Certificate2(certData); + + httpClientHandler.ClientCertificates.Add(cert); + } + + var httpClient = new HttpClient(httpClientHandler); + + var channel = GrpcChannel.ForAddress($"{scheme}://{options.ServerHost}:{options.ServerPort}", new GrpcChannelOptions + { + Credentials = credentials, + HttpClient = httpClient, + LoggerFactory = loggerFactory + }); + + return new GrpcChannelWrapper(channel); + } + + private bool IsHttpClient() => string.Equals(options.ClientType, "httpclient", StringComparison.OrdinalIgnoreCase); + + private async Task CreateCredentialsAsync(bool? useTestCaOverride = null) + { + var credentials = ChannelCredentials.Insecure; + if (options.UseTls.GetValueOrDefault()) + { + credentials = new SslCredentials(); + } + + if (options.TestCase == "jwt_token_creds") + { + var googleCredential = await GoogleCredential.GetApplicationDefaultAsync(); + Assert.IsTrue(googleCredential.IsCreateScopedRequired); + credentials = ChannelCredentials.Create(credentials, googleCredential.ToCallCredentials()); + } + + if (options.TestCase == "compute_engine_creds") + { + var googleCredential = await GoogleCredential.GetApplicationDefaultAsync(); + Assert.IsFalse(googleCredential.IsCreateScopedRequired); + credentials = ChannelCredentials.Create(credentials, googleCredential.ToCallCredentials()); + } + return credentials; + } + + private TClient CreateClient(IChannelWrapper channel) where TClient : ClientBase + { + return (TClient)Activator.CreateInstance(typeof(TClient), channel.Channel)!; + } + + private async Task RunTestCaseAsync(IChannelWrapper channel, ClientOptions options) + { + var client = CreateClient(channel); + switch (options.TestCase) + { + case "empty_unary": + RunEmptyUnary(client); + break; + case "large_unary": + RunLargeUnary(client); + break; + case "client_streaming": + await RunClientStreamingAsync(client); + break; + case "server_streaming": + await RunServerStreamingAsync(client); + break; + case "ping_pong": + await RunPingPongAsync(client); + break; + case "empty_stream": + await RunEmptyStreamAsync(client); + break; + case "compute_engine_creds": + RunComputeEngineCreds(client, options.DefaultServiceAccount!, options.OAuthScope!); + break; + case "jwt_token_creds": + RunJwtTokenCreds(client); + break; + case "oauth2_auth_token": + await RunOAuth2AuthTokenAsync(client, options.OAuthScope!); + break; + case "per_rpc_creds": + await RunPerRpcCredsAsync(client, options.OAuthScope!); + break; + case "cancel_after_begin": + await RunCancelAfterBeginAsync(client); + break; + case "cancel_after_first_response": + await RunCancelAfterFirstResponseAsync(client); + break; + case "timeout_on_sleeping_server": + await RunTimeoutOnSleepingServerAsync(client); + break; + case "custom_metadata": + await RunCustomMetadataAsync(client); + break; + case "status_code_and_message": + await RunStatusCodeAndMessageAsync(client); + break; + case "unimplemented_service": + RunUnimplementedService(CreateClient(channel)); + break; + case "special_status_message": + await RunSpecialStatusMessageAsync(client); + break; + case "unimplemented_method": + RunUnimplementedMethod(client); + break; + case "client_compressed_unary": + RunClientCompressedUnary(client); + break; + case "client_compressed_streaming": + await RunClientCompressedStreamingAsync(client); + break; + case "server_compressed_unary": + await RunServerCompressedUnary(client); + break; + case "server_compressed_streaming": + await RunServerCompressedStreamingAsync(client); + break; + default: + throw new ArgumentException("Unknown test case " + options.TestCase); + } + } + + public static void RunEmptyUnary(TestService.TestServiceClient client) + { + Console.WriteLine("running empty_unary"); + var response = client.EmptyCall(new Empty()); + Assert.IsNotNull(response); + Console.WriteLine("Passed!"); + } + + public static void RunLargeUnary(TestService.TestServiceClient client) + { + Console.WriteLine("running large_unary"); + var request = new SimpleRequest + { + ResponseSize = 314159, + Payload = CreateZerosPayload(271828) + }; + var response = client.UnaryCall(request); + + Assert.AreEqual(314159, response.Payload.Body.Length); + Console.WriteLine("Passed!"); + } + + public static async Task RunClientStreamingAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running client_streaming"); + + var bodySizes = new List { 27182, 8, 1828, 45904 }.Select((size) => new StreamingInputCallRequest { Payload = CreateZerosPayload(size) }); + + using (var call = client.StreamingInputCall()) + { + await call.RequestStream.WriteAllAsync(bodySizes); + + var response = await call.ResponseAsync; + Assert.AreEqual(74922, response.AggregatedPayloadSize); + } + Console.WriteLine("Passed!"); + } + + public static async Task RunServerStreamingAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running server_streaming"); + + var bodySizes = new List { 31415, 9, 2653, 58979 }; + + var request = new StreamingOutputCallRequest + { + ResponseParameters = { bodySizes.Select((size) => new ResponseParameters { Size = size }) } + }; + + using (var call = client.StreamingOutputCall(request)) + { + var responseList = await call.ResponseStream.ToListAsync(); + CollectionAssert.AreEqual(bodySizes, responseList.Select((item) => item.Payload.Body.Length).ToList()); + } + Console.WriteLine("Passed!"); + } + + public static async Task RunPingPongAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running ping_pong"); + + using (var call = client.FullDuplexCall()) + { + await call.RequestStream.WriteAsync(new StreamingOutputCallRequest + { + ResponseParameters = { new ResponseParameters { Size = 31415 } }, + Payload = CreateZerosPayload(27182) + }); + + Assert.IsTrue(await call.ResponseStream.MoveNext()); + Assert.AreEqual(31415, call.ResponseStream.Current.Payload.Body.Length); + + await call.RequestStream.WriteAsync(new StreamingOutputCallRequest + { + ResponseParameters = { new ResponseParameters { Size = 9 } }, + Payload = CreateZerosPayload(8) + }); + + Assert.IsTrue(await call.ResponseStream.MoveNext()); + Assert.AreEqual(9, call.ResponseStream.Current.Payload.Body.Length); + + await call.RequestStream.WriteAsync(new StreamingOutputCallRequest + { + ResponseParameters = { new ResponseParameters { Size = 2653 } }, + Payload = CreateZerosPayload(1828) + }); + + Assert.IsTrue(await call.ResponseStream.MoveNext()); + Assert.AreEqual(2653, call.ResponseStream.Current.Payload.Body.Length); + + await call.RequestStream.WriteAsync(new StreamingOutputCallRequest + { + ResponseParameters = { new ResponseParameters { Size = 58979 } }, + Payload = CreateZerosPayload(45904) + }); + + Assert.IsTrue(await call.ResponseStream.MoveNext()); + Assert.AreEqual(58979, call.ResponseStream.Current.Payload.Body.Length); + + await call.RequestStream.CompleteAsync(); + + Assert.IsFalse(await call.ResponseStream.MoveNext()); + } + Console.WriteLine("Passed!"); + } + + public static async Task RunEmptyStreamAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running empty_stream"); + using (var call = client.FullDuplexCall()) + { + await call.RequestStream.CompleteAsync(); + + var responseList = await call.ResponseStream.ToListAsync(); + Assert.AreEqual(0, responseList.Count); + } + Console.WriteLine("Passed!"); + } + + public static void RunComputeEngineCreds(TestService.TestServiceClient client, string defaultServiceAccount, string oauthScope) + { + Console.WriteLine("running compute_engine_creds"); + + var request = new SimpleRequest + { + ResponseSize = 314159, + Payload = CreateZerosPayload(271828), + FillUsername = true, + FillOauthScope = true + }; + + // not setting credentials here because they were set on channel already + var response = client.UnaryCall(request); + + Assert.AreEqual(314159, response.Payload.Body.Length); + Assert.IsFalse(string.IsNullOrEmpty(response.OauthScope)); + Assert.IsTrue(oauthScope.Contains(response.OauthScope)); + Assert.AreEqual(defaultServiceAccount, response.Username); + Console.WriteLine("Passed!"); + } + + public static void RunJwtTokenCreds(TestService.TestServiceClient client) + { + Console.WriteLine("running jwt_token_creds"); + + var request = new SimpleRequest + { + ResponseSize = 314159, + Payload = CreateZerosPayload(271828), + FillUsername = true, + }; + + // not setting credentials here because they were set on channel already + var response = client.UnaryCall(request); + + Assert.AreEqual(314159, response.Payload.Body.Length); + Assert.AreEqual(GetEmailFromServiceAccountFile(), response.Username); + Console.WriteLine("Passed!"); + } + + public static async Task RunOAuth2AuthTokenAsync(TestService.TestServiceClient client, string oauthScope) + { + Console.WriteLine("running oauth2_auth_token"); + ITokenAccess credential = (await GoogleCredential.GetApplicationDefaultAsync()).CreateScoped(new[] { oauthScope }); + string oauth2Token = await credential.GetAccessTokenForRequestAsync(); + + var credentials = GoogleGrpcCredentials.FromAccessToken(oauth2Token); + var request = new SimpleRequest + { + FillUsername = true, + FillOauthScope = true + }; + + var response = client.UnaryCall(request, new CallOptions(credentials: credentials)); + + Assert.IsFalse(string.IsNullOrEmpty(response.OauthScope)); + Assert.IsTrue(oauthScope.Contains(response.OauthScope)); + Assert.AreEqual(GetEmailFromServiceAccountFile(), response.Username); + Console.WriteLine("Passed!"); + } + + public static async Task RunPerRpcCredsAsync(TestService.TestServiceClient client, string oauthScope) + { + Console.WriteLine("running per_rpc_creds"); + ITokenAccess googleCredential = await GoogleCredential.GetApplicationDefaultAsync(); + + var credentials = googleCredential.ToCallCredentials(); + var request = new SimpleRequest + { + FillUsername = true, + }; + + var response = client.UnaryCall(request, new CallOptions(credentials: credentials)); + + Assert.AreEqual(GetEmailFromServiceAccountFile(), response.Username); + Console.WriteLine("Passed!"); + } + + public static async Task RunCancelAfterBeginAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running cancel_after_begin"); + + var cts = new CancellationTokenSource(); + using (var call = client.StreamingInputCall(cancellationToken: cts.Token)) + { + // TODO(jtattermusch): we need this to ensure call has been initiated once we cancel it. + await Task.Delay(1000); + cts.Cancel(); + + var ex = await Assert.ThrowsAsync(() => call.ResponseAsync); + Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode); + } + Console.WriteLine("Passed!"); + } + + public static async Task RunCancelAfterFirstResponseAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running cancel_after_first_response"); + + var cts = new CancellationTokenSource(); + using (var call = client.FullDuplexCall(cancellationToken: cts.Token)) + { + await call.RequestStream.WriteAsync(new StreamingOutputCallRequest + { + ResponseParameters = { new ResponseParameters { Size = 31415 } }, + Payload = CreateZerosPayload(27182) + }); + + Assert.IsTrue(await call.ResponseStream.MoveNext()); + Assert.AreEqual(31415, call.ResponseStream.Current.Payload.Body.Length); + + cts.Cancel(); + + try + { + // cannot use Assert.ThrowsAsync because it uses Task.Wait and would deadlock. + await call.ResponseStream.MoveNext(); + Assert.Fail(); + } + catch (RpcException ex) + { + Assert.AreEqual(StatusCode.Cancelled, ex.Status.StatusCode); + } + } + Console.WriteLine("Passed!"); + } + + public static async Task RunTimeoutOnSleepingServerAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running timeout_on_sleeping_server"); + + var deadline = DateTime.UtcNow.AddMilliseconds(1); + using (var call = client.FullDuplexCall(deadline: deadline)) + { + try + { + await call.RequestStream.WriteAsync(new StreamingOutputCallRequest { Payload = CreateZerosPayload(27182) }); + } + catch (InvalidOperationException) + { + // Deadline was reached before write has started. Eat the exception and continue. + } + catch (RpcException) + { + // Deadline was reached before write has started. Eat the exception and continue. + } + + try + { + await call.ResponseStream.MoveNext(); + Assert.Fail(); + } + catch (RpcException ex) + { + Assert.AreEqual(StatusCode.DeadlineExceeded, ex.StatusCode); + } + } + Console.WriteLine("Passed!"); + } + + public static async Task RunCustomMetadataAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running custom_metadata"); + { + // step 1: test unary call + var request = new SimpleRequest + { + ResponseSize = 314159, + Payload = CreateZerosPayload(271828) + }; + + var call = client.UnaryCallAsync(request, headers: CreateTestMetadata()); + await call.ResponseAsync; + + var responseHeaders = await call.ResponseHeadersAsync; + var responseTrailers = call.GetTrailers(); + + Assert.AreEqual("test_initial_metadata_value", responseHeaders.First((entry) => entry.Key == "x-grpc-test-echo-initial").Value); + CollectionAssert.AreEqual(new byte[] { 0xab, 0xab, 0xab }, responseTrailers.First((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ValueBytes); + } + + { + // step 2: test full duplex call + var request = new StreamingOutputCallRequest + { + ResponseParameters = { new ResponseParameters { Size = 31415 } }, + Payload = CreateZerosPayload(27182) + }; + + var call = client.FullDuplexCall(headers: CreateTestMetadata()); + + await call.RequestStream.WriteAsync(request); + await call.RequestStream.CompleteAsync(); + await call.ResponseStream.ToListAsync(); + + var responseHeaders = await call.ResponseHeadersAsync; + var responseTrailers = call.GetTrailers(); + + Assert.AreEqual("test_initial_metadata_value", responseHeaders.First((entry) => entry.Key == "x-grpc-test-echo-initial").Value); + CollectionAssert.AreEqual(new byte[] { 0xab, 0xab, 0xab }, responseTrailers.First((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ValueBytes); + } + + Console.WriteLine("Passed!"); + } + + public static async Task RunStatusCodeAndMessageAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running status_code_and_message"); + var echoStatus = new EchoStatus + { + Code = 2, + Message = "test status message" + }; + + { + // step 1: test unary call + var request = new SimpleRequest { ResponseStatus = echoStatus }; + + var e = Assert.Throws(() => client.UnaryCall(request)); + Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); + Assert.AreEqual(echoStatus.Message, e.Status.Detail); + } + + { + // step 2: test full duplex call + var request = new StreamingOutputCallRequest { ResponseStatus = echoStatus }; + + var call = client.FullDuplexCall(); + await call.RequestStream.WriteAsync(request); + await call.RequestStream.CompleteAsync(); + + try + { + // cannot use Assert.ThrowsAsync because it uses Task.Wait and would deadlock. + await call.ResponseStream.ToListAsync(); + Assert.Fail(); + } + catch (RpcException e) + { + Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); + Assert.AreEqual(echoStatus.Message, e.Status.Detail); + } + } + + Console.WriteLine("Passed!"); + } + + private static async Task RunSpecialStatusMessageAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running special_status_message"); + + var echoStatus = new EchoStatus + { + Code = 2, + Message = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n" + }; + + try + { + await client.UnaryCallAsync(new SimpleRequest + { + ResponseStatus = echoStatus + }); + Assert.Fail(); + } + catch (RpcException e) + { + Assert.AreEqual(StatusCode.Unknown, e.Status.StatusCode); + Assert.AreEqual(echoStatus.Message, e.Status.Detail); + } + + Console.WriteLine("Passed!"); + } + + public static void RunUnimplementedService(UnimplementedService.UnimplementedServiceClient client) + { + Console.WriteLine("running unimplemented_service"); + var e = Assert.Throws(() => client.UnimplementedCall(new Empty())); + + Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode); + Console.WriteLine("Passed!"); + } + + public static void RunUnimplementedMethod(TestService.TestServiceClient client) + { + Console.WriteLine("running unimplemented_method"); + var e = Assert.Throws(() => client.UnimplementedCall(new Empty())); + + Assert.AreEqual(StatusCode.Unimplemented, e.Status.StatusCode); + Console.WriteLine("Passed!"); + } + + public static void RunClientCompressedUnary(TestService.TestServiceClient client) + { + Console.WriteLine("running client_compressed_unary"); + var probeRequest = new SimpleRequest + { + ExpectCompressed = new BoolValue + { + Value = true // lie about compression + }, + ResponseSize = 314159, + Payload = CreateZerosPayload(271828) + }; + var e = Assert.Throws(() => client.UnaryCall(probeRequest, CreateClientCompressionMetadata(false))); + Assert.AreEqual(StatusCode.InvalidArgument, e.Status.StatusCode); + + var compressedRequest = new SimpleRequest + { + ExpectCompressed = new BoolValue + { + Value = true + }, + ResponseSize = 314159, + Payload = CreateZerosPayload(271828) + }; + var response1 = client.UnaryCall(compressedRequest, CreateClientCompressionMetadata(true)); + Assert.AreEqual(314159, response1.Payload.Body.Length); + + var uncompressedRequest = new SimpleRequest + { + ExpectCompressed = new BoolValue + { + Value = false + }, + ResponseSize = 314159, + Payload = CreateZerosPayload(271828) + }; + var response2 = client.UnaryCall(uncompressedRequest, CreateClientCompressionMetadata(false)); + Assert.AreEqual(314159, response2.Payload.Body.Length); + + Console.WriteLine("Passed!"); + } + + public static async Task RunClientCompressedStreamingAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running client_compressed_streaming"); + try + { + var probeCall = client.StreamingInputCall(CreateClientCompressionMetadata(false)); + await probeCall.RequestStream.WriteAsync(new StreamingInputCallRequest + { + ExpectCompressed = new BoolValue + { + Value = true + }, + Payload = CreateZerosPayload(27182) + }); + + // cannot use Assert.ThrowsAsync because it uses Task.Wait and would deadlock. + await probeCall; + Assert.Fail(); + } + catch (RpcException e) + { + Assert.AreEqual(StatusCode.InvalidArgument, e.Status.StatusCode); + } + + var call = client.StreamingInputCall(CreateClientCompressionMetadata(true)); + await call.RequestStream.WriteAsync(new StreamingInputCallRequest + { + ExpectCompressed = new BoolValue + { + Value = true + }, + Payload = CreateZerosPayload(27182) + }); + + call.RequestStream.WriteOptions = new WriteOptions(WriteFlags.NoCompress); + await call.RequestStream.WriteAsync(new StreamingInputCallRequest + { + ExpectCompressed = new BoolValue + { + Value = false + }, + Payload = CreateZerosPayload(45904) + }); + await call.RequestStream.CompleteAsync(); + + var response = await call.ResponseAsync; + Assert.AreEqual(73086, response.AggregatedPayloadSize); + + Console.WriteLine("Passed!"); + } + + public static async Task RunServerCompressedUnary(TestService.TestServiceClient client) + { + Console.WriteLine("running server_compressed_unary"); + + var request = new SimpleRequest + { + ResponseSize = 314159, + Payload = CreateZerosPayload(271828), + ResponseCompressed = new BoolValue { Value = true } + }; + var response = await client.UnaryCallAsync(request); + + // Compression of response message is not verified because there is no API available + Assert.AreEqual(314159, response.Payload.Body.Length); + + request = new SimpleRequest + { + ResponseSize = 314159, + Payload = CreateZerosPayload(271828), + ResponseCompressed = new BoolValue { Value = false } + }; + response = await client.UnaryCallAsync(request); + + // Compression of response message is not verified because there is no API available + Assert.AreEqual(314159, response.Payload.Body.Length); + + Console.WriteLine("Passed!"); + } + + public static async Task RunServerCompressedStreamingAsync(TestService.TestServiceClient client) + { + Console.WriteLine("running server_compressed_streaming"); + + var bodySizes = new List { 31415, 92653 }; + + var request = new StreamingOutputCallRequest + { + ResponseParameters = { bodySizes.Select((size) => new ResponseParameters { Size = size, Compressed = new BoolValue { Value = true } }) } + }; + + using (var call = client.StreamingOutputCall(request)) + { + // Compression of response message is not verified because there is no API available + var responseList = await call.ResponseStream.ToListAsync(); + CollectionAssert.AreEqual(bodySizes, responseList.Select((item) => item.Payload.Body.Length).ToList()); + } + + Console.WriteLine("Passed!"); + } + + private static Payload CreateZerosPayload(int size) + { + return new Payload { Body = ByteString.CopyFrom(new byte[size]) }; + } + + private static Metadata CreateClientCompressionMetadata(bool compressed) + { + var algorithmName = compressed ? "gzip" : "identity"; + return new Metadata + { + { new Metadata.Entry(CompressionRequestAlgorithmMetadataKey, algorithmName) } + }; + } + + // extracts the client_email field from service account file used for auth test cases + private static string GetEmailFromServiceAccountFile() + { + string keyFile = Environment.GetEnvironmentVariable("GOOGLE_APPLICATION_CREDENTIALS")!; + Assert.IsNotNull(keyFile); + var jobject = JObject.Parse(File.ReadAllText(keyFile)); + string email = jobject.GetValue("client_email").Value(); + Assert.IsTrue(email.Length > 0); // spec requires nonempty client email. + return email; + } + + private static Metadata CreateTestMetadata() + { + return new Metadata + { + {"x-grpc-test-echo-initial", "test_initial_metadata_value"}, + {"x-grpc-test-echo-trailing-bin", new byte[] {0xab, 0xab, 0xab}} + }; + } + + // TODO(JamesNK): PEM loading logic from https://stackoverflow.com/a/10498045/11829 + // .NET does not have a built-in API for loading pem files + // Consider providing ca file in a different format and removing method + private byte[]? GetBytesFromPem(string pemString, string section) + { + var header = string.Format("-----BEGIN {0}-----", section); + var footer = string.Format("-----END {0}-----", section); + + var start = pemString.IndexOf(header, StringComparison.Ordinal); + if (start == -1) + { + return null; + } + + start += header.Length; + var end = pemString.IndexOf(footer, start, StringComparison.Ordinal) - start; + + if (end == -1) + { + return null; + } + + return Convert.FromBase64String(pemString.Substring(start, end)); + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj b/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj new file mode 100644 index 000000000000..dafe7340b7da --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj @@ -0,0 +1,25 @@ + + + + Exe + netcoreapp5.0 + enable + 8.0 + + + + + + + + + + + + + + + + + + diff --git a/src/Grpc/test/testassets/InteropTestsClient/Program.cs b/src/Grpc/test/testassets/InteropTestsClient/Program.cs new file mode 100644 index 000000000000..3fc3d9d9757d --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/Program.cs @@ -0,0 +1,32 @@ +#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; + +namespace InteropTestsClient +{ + public class Program + { + public static void Main(string[] args) + { + Console.WriteLine("Application started."); + + InteropClient.Run(args); + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsClient/RunTests.ps1 b/src/Grpc/test/testassets/InteropTestsClient/RunTests.ps1 new file mode 100644 index 000000000000..faa46d98c1b9 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsClient/RunTests.ps1 @@ -0,0 +1,53 @@ +Param +( + [bool]$use_tls = $false +) + +$allTests = + "empty_unary", + "large_unary", + "client_streaming", + "server_streaming", + "ping_pong", + "empty_stream", + + #"compute_engine_creds", + #"jwt_token_creds", + #"oauth2_auth_token", + #"per_rpc_creds", + + "cancel_after_begin", + "cancel_after_first_response", + "timeout_on_sleeping_server", + "custom_metadata", + "status_code_and_message", + "special_status_message", + "unimplemented_service", + "unimplemented_method", + "client_compressed_unary", + "client_compressed_streaming", + "server_compressed_unary", + "server_compressed_streaming" + +Write-Host "Running $($allTests.Count) tests" -ForegroundColor Cyan +Write-Host "Use TLS: $use_tls" -ForegroundColor Cyan +Write-Host + +foreach ($test in $allTests) +{ + Write-Host "Running $test" -ForegroundColor Cyan + + if (!$use_tls) + { + dotnet run --use_tls false --server_port 50052 --client_type httpclient --test_case $test + } + else + { + # Certificate is for test.google.com host. To run locally, setup the host file to point test.google.com to 127.0.0.1 + dotnet run --use_tls true --server_port 50052 --client_type httpclient --test_case $test --server_host test.google.com + } + + Write-Host +} + +Write-Host "Done" -ForegroundColor Cyan \ No newline at end of file diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/AsyncStreamExtensions.cs b/src/Grpc/test/testassets/InteropTestsWebsite/AsyncStreamExtensions.cs new file mode 100644 index 000000000000..1f296cb7ba38 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsWebsite/AsyncStreamExtensions.cs @@ -0,0 +1,41 @@ +#region Copyright notice and license + +// Copyright 2015 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.Collections.Generic; +using System.Threading.Tasks; +using Grpc.Core; + +namespace InteropTestsWebsite +{ + // Implementation copied from https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.Core/Utils/AsyncStreamExtensions.cs + internal static class AsyncStreamExtensions + { + /// + /// Reads the entire stream and executes an async action for each element. + /// + public static async Task ForEachAsync(this IAsyncStreamReader streamReader, Func asyncAction) + where T : class + { + while (await streamReader.MoveNext().ConfigureAwait(false)) + { + await asyncAction(streamReader.Current).ConfigureAwait(false); + } + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj b/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj new file mode 100644 index 000000000000..6fd525dead53 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj @@ -0,0 +1,19 @@ + + + + netcoreapp5.0 + InProcess + false + enable + 8.0 + + + + + + + + + + + diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/Program.cs b/src/Grpc/test/testassets/InteropTestsWebsite/Program.cs new file mode 100644 index 000000000000..b841b3528b52 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsWebsite/Program.cs @@ -0,0 +1,65 @@ +#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 Microsoft.AspNetCore; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Server.Kestrel.Core; +using Microsoft.Extensions.Configuration; +using Microsoft.Extensions.Hosting; + +namespace InteropTestsWebsite +{ + 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.ConfigureKestrel((context, options) => + { + // Support --port and --use_tls cmdline arguments normally supported + // by gRPC interop servers. + var port = context.Configuration.GetValue("port", 50052); + var useTls = context.Configuration.GetValue("use_tls", false); + + options.Limits.MinRequestBodyDataRate = null; + options.ListenAnyIP(port, listenOptions => + { + Console.WriteLine($"Enabling connection encryption: {useTls}"); + + if (useTls) + { + var basePath = Path.GetDirectoryName(typeof(Program).Assembly.Location); + var certPath = Path.Combine(basePath!, "Certs", "server1.pfx"); + + listenOptions.UseHttps(certPath, "1111"); + } + listenOptions.Protocols = HttpProtocols.Http2; + }); + }); + webBuilder.UseStartup(); + }); + } +} diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/README.md b/src/Grpc/test/testassets/InteropTestsWebsite/README.md new file mode 100644 index 000000000000..91c7085da85e --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsWebsite/README.md @@ -0,0 +1,27 @@ +Running Grpc.Core interop client against Grpc.AspNetCore.Server interop server. +Context: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md + +## Start the InteropTestsWebsite + +``` +# From this directory +$ dotnet run +Now listening on: http://localhost:50052 +``` + +## Build gRPC C# as a developer: +Follow https://github.com/grpc/grpc/tree/master/src/csharp +``` +python tools/run_tests/run_tests.py -l csharp -c dbg --build_only +``` + +## Running the interop client + +``` +cd src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 + +mono Grpc.IntegrationTesting.Client.exe --server_host=localhost --server_port=50052 --test_case=large_unary +``` + +NOTE: Currently the some tests will fail because not all the features are implemented +by Grpc.AspNetCore.Server diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/Startup.cs b/src/Grpc/test/testassets/InteropTestsWebsite/Startup.cs new file mode 100644 index 000000000000..4715d1fbbca4 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsWebsite/Startup.cs @@ -0,0 +1,49 @@ +#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 Grpc.Testing; +using Microsoft.AspNetCore.Builder; +using Microsoft.Extensions.DependencyInjection; +using Microsoft.Extensions.Hosting; + +namespace InteropTestsWebsite +{ + public class Startup + { + // This method gets called by the runtime. Use this method to add services to the container. + // For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940 + public void ConfigureServices(IServiceCollection services) + { + services.AddGrpc(); + } + + // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. + public void Configure(IApplicationBuilder app, IHostApplicationLifetime applicationLifetime) + { + // Required to notify test infrastructure that it can begin tests + applicationLifetime.ApplicationStarted.Register(() => Console.WriteLine("Application started.")); + + app.UseRouting(); + app.UseEndpoints(endpoints => + { + endpoints.MapGrpcService(); + }); + } + } +} diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/TestServiceImpl.cs b/src/Grpc/test/testassets/InteropTestsWebsite/TestServiceImpl.cs new file mode 100644 index 000000000000..918500cd2562 --- /dev/null +++ b/src/Grpc/test/testassets/InteropTestsWebsite/TestServiceImpl.cs @@ -0,0 +1,149 @@ +#region Copyright notice and license + +// Copyright 2015-2016 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.Linq; +using System.Threading.Tasks; +using Google.Protobuf; +using Grpc.Core; +using InteropTestsWebsite; + +namespace Grpc.Testing +{ + // Implementation copied from https://github.com/grpc/grpc/blob/master/src/csharp/Grpc.IntegrationTesting/TestServiceImpl.cs + public class TestServiceImpl : TestService.TestServiceBase + { + public override Task EmptyCall(Empty request, ServerCallContext context) + { + return Task.FromResult(new Empty()); + } + + public override async Task UnaryCall(SimpleRequest request, ServerCallContext context) + { + await EnsureEchoMetadataAsync(context, request.ResponseCompressed?.Value ?? false); + EnsureEchoStatus(request.ResponseStatus, context); + EnsureCompression(request.ExpectCompressed, context); + + var response = new SimpleResponse { Payload = CreateZerosPayload(request.ResponseSize) }; + return response; + } + + public override async Task StreamingOutputCall(StreamingOutputCallRequest request, IServerStreamWriter responseStream, ServerCallContext context) + { + await EnsureEchoMetadataAsync(context, request.ResponseParameters.Any(rp => rp.Compressed?.Value ?? false)); + EnsureEchoStatus(request.ResponseStatus, context); + + foreach (var responseParam in request.ResponseParameters) + { + responseStream.WriteOptions = !(responseParam.Compressed?.Value ?? false) + ? new WriteOptions(WriteFlags.NoCompress) + : null; + + var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) }; + await responseStream.WriteAsync(response); + } + } + + public override async Task StreamingInputCall(IAsyncStreamReader requestStream, ServerCallContext context) + { + await EnsureEchoMetadataAsync(context); + + int sum = 0; + await requestStream.ForEachAsync(request => + { + EnsureCompression(request.ExpectCompressed, context); + + sum += request.Payload.Body.Length; + return Task.CompletedTask; + }); + return new StreamingInputCallResponse { AggregatedPayloadSize = sum }; + } + + public override async Task FullDuplexCall(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + { + await EnsureEchoMetadataAsync(context); + + await requestStream.ForEachAsync(async request => + { + EnsureEchoStatus(request.ResponseStatus, context); + foreach (var responseParam in request.ResponseParameters) + { + var response = new StreamingOutputCallResponse { Payload = CreateZerosPayload(responseParam.Size) }; + await responseStream.WriteAsync(response); + } + }); + } + + public override Task HalfDuplexCall(IAsyncStreamReader requestStream, IServerStreamWriter responseStream, ServerCallContext context) + { + throw new NotImplementedException(); + } + + private static Payload CreateZerosPayload(int size) + { + return new Payload { Body = ByteString.CopyFrom(new byte[size]) }; + } + + private static async Task EnsureEchoMetadataAsync(ServerCallContext context, bool enableCompression = false) + { + var echoInitialList = context.RequestHeaders.Where((entry) => entry.Key == "x-grpc-test-echo-initial").ToList(); + + // Append grpc internal compression header if compression is requested by the client + if (enableCompression) + { + echoInitialList.Add(new Metadata.Entry("grpc-internal-encoding-request", "gzip")); + } + + if (echoInitialList.Any()) { + var entry = echoInitialList.Single(); + await context.WriteResponseHeadersAsync(new Metadata { entry }); + } + + var echoTrailingList = context.RequestHeaders.Where((entry) => entry.Key == "x-grpc-test-echo-trailing-bin").ToList(); + if (echoTrailingList.Any()) { + context.ResponseTrailers.Add(echoTrailingList.Single()); + } + } + + private static void EnsureEchoStatus(EchoStatus responseStatus, ServerCallContext context) + { + if (responseStatus != null) + { + var statusCode = (StatusCode)responseStatus.Code; + context.Status = new Status(statusCode, responseStatus.Message); + } + } + + private static void EnsureCompression(BoolValue? expectCompressed, ServerCallContext context) + { + if (expectCompressed != null) + { + // ServerCallContext.RequestHeaders filters out grpc-* headers + // Get grpc-encoding from HttpContext instead + var encoding = context.GetHttpContext().Request.Headers.SingleOrDefault(h => h.Key == "grpc-encoding").Value.SingleOrDefault(); + if (expectCompressed.Value) + { + if (encoding == null || encoding == "identity") + { + throw new RpcException(new Status(StatusCode.InvalidArgument, string.Empty)); + } + } + } + } + } +} diff --git a/src/Grpc/test/testassets/Proto/grpc/testing/empty.proto b/src/Grpc/test/testassets/Proto/grpc/testing/empty.proto new file mode 100644 index 000000000000..6a0aa88dfde1 --- /dev/null +++ b/src/Grpc/test/testassets/Proto/grpc/testing/empty.proto @@ -0,0 +1,28 @@ + +// Copyright 2015 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 grpc.testing; + +// An empty message that you can re-use to avoid defining duplicated empty +// messages in your project. A typical example is to use it as argument or the +// return value of a service API. For instance: +// +// service Foo { +// rpc Bar (grpc.testing.Empty) returns (grpc.testing.Empty) { }; +// }; +// +message Empty {} diff --git a/src/Grpc/test/testassets/Proto/grpc/testing/messages.proto b/src/Grpc/test/testassets/Proto/grpc/testing/messages.proto new file mode 100644 index 000000000000..7b1b7286dced --- /dev/null +++ b/src/Grpc/test/testassets/Proto/grpc/testing/messages.proto @@ -0,0 +1,165 @@ + +// Copyright 2015-2016 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. + +// Message definitions to be used by integration test service definitions. + +syntax = "proto3"; + +package grpc.testing; + +// TODO(dgq): Go back to using well-known types once +// https://github.com/grpc/grpc/issues/6980 has been fixed. +// import "google/protobuf/wrappers.proto"; +message BoolValue { + // The bool value. + bool value = 1; +} + +// The type of payload that should be returned. +enum PayloadType { + // Compressable text format. + COMPRESSABLE = 0; +} + +// A block of data, to simply increase gRPC message size. +message Payload { + // The type of data in body. + PayloadType type = 1; + // Primary contents of payload. + bytes body = 2; +} + +// A protobuf representation for grpc status. This is used by test +// clients to specify a status that the server should attempt to return. +message EchoStatus { + int32 code = 1; + string message = 2; +} + +// Unary request. +message SimpleRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, server randomly chooses one from other formats. + PayloadType response_type = 1; + + // Desired payload size in the response from the server. + int32 response_size = 2; + + // Optional input payload sent along with the request. + Payload payload = 3; + + // Whether SimpleResponse should include username. + bool fill_username = 4; + + // Whether SimpleResponse should include OAuth scope. + bool fill_oauth_scope = 5; + + // Whether to request the server to compress the response. This field is + // "nullable" in order to interoperate seamlessly with clients not able to + // implement the full compression tests by introspecting the call to verify + // the response's compression status. + BoolValue response_compressed = 6; + + // Whether server should return a given status + EchoStatus response_status = 7; + + // Whether the server should expect this request to be compressed. + BoolValue expect_compressed = 8; +} + +// Unary response, as configured by the request. +message SimpleResponse { + // Payload to increase message size. + Payload payload = 1; + // The user the request came from, for verifying authentication was + // successful when the client expected it. + string username = 2; + // OAuth scope. + string oauth_scope = 3; +} + +// Client-streaming request. +message StreamingInputCallRequest { + // Optional input payload sent along with the request. + Payload payload = 1; + + // Whether the server should expect this request to be compressed. This field + // is "nullable" in order to interoperate seamlessly with servers not able to + // implement the full compression tests by introspecting the call to verify + // the request's compression status. + BoolValue expect_compressed = 2; + + // Not expecting any payload from the response. +} + +// Client-streaming response. +message StreamingInputCallResponse { + // Aggregated size of payloads received from the client. + int32 aggregated_payload_size = 1; +} + +// Configuration for a particular response. +message ResponseParameters { + // Desired payload sizes in responses from the server. + int32 size = 1; + + // Desired interval between consecutive responses in the response stream in + // microseconds. + int32 interval_us = 2; + + // Whether to request the server to compress the response. This field is + // "nullable" in order to interoperate seamlessly with clients not able to + // implement the full compression tests by introspecting the call to verify + // the response's compression status. + BoolValue compressed = 3; +} + +// Server-streaming request. +message StreamingOutputCallRequest { + // Desired payload type in the response from the server. + // If response_type is RANDOM, the payload from each response in the stream + // might be of different types. This is to simulate a mixed type of payload + // stream. + PayloadType response_type = 1; + + // Configuration for each expected response message. + repeated ResponseParameters response_parameters = 2; + + // Optional input payload sent along with the request. + Payload payload = 3; + + // Whether server should return a given status + EchoStatus response_status = 7; +} + +// Server-streaming response, as configured by the request and parameters. +message StreamingOutputCallResponse { + // Payload to increase response size. + Payload payload = 1; +} + +// For reconnect interop test only. +// Client tells server what reconnection parameters it used. +message ReconnectParams { + int32 max_reconnect_backoff_ms = 1; +} + +// For reconnect interop test only. +// Server tells client whether its reconnects are following the spec and the +// reconnect backoffs it saw. +message ReconnectInfo { + bool passed = 1; + repeated int32 backoff_ms = 2; +} diff --git a/src/Grpc/test/testassets/Proto/grpc/testing/test.proto b/src/Grpc/test/testassets/Proto/grpc/testing/test.proto new file mode 100644 index 000000000000..86d6ab60506a --- /dev/null +++ b/src/Grpc/test/testassets/Proto/grpc/testing/test.proto @@ -0,0 +1,79 @@ + +// Copyright 2015-2016 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. + +// An integration test service that covers all the method signature permutations +// of unary/streaming requests/responses. + +syntax = "proto3"; + +import "empty.proto"; +import "messages.proto"; + +package grpc.testing; + +// A simple service to test the various types of RPCs and experiment with +// performance with various types of payload. +service TestService { + // One empty request followed by one empty response. + rpc EmptyCall(grpc.testing.Empty) returns (grpc.testing.Empty); + + // One request followed by one response. + rpc UnaryCall(SimpleRequest) returns (SimpleResponse); + + // One request followed by one response. Response has cache control + // headers set such that a caching HTTP proxy (such as GFE) can + // satisfy subsequent requests. + rpc CacheableUnaryCall(SimpleRequest) returns (SimpleResponse); + + // One request followed by a sequence of responses (streamed download). + // The server returns the payload with client desired type and sizes. + rpc StreamingOutputCall(StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by one response (streamed upload). + // The server returns the aggregated size of client payload as the result. + rpc StreamingInputCall(stream StreamingInputCallRequest) + returns (StreamingInputCallResponse); + + // A sequence of requests with each request served by the server immediately. + // As one request could lead to multiple responses, this interface + // demonstrates the idea of full duplexing. + rpc FullDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // A sequence of requests followed by a sequence of responses. + // The server buffers all the client requests and then serves them in order. A + // stream of responses are returned to the client when the server starts with + // first request. + rpc HalfDuplexCall(stream StreamingOutputCallRequest) + returns (stream StreamingOutputCallResponse); + + // The test server will not implement this method. It will be used + // to test the behavior when clients call unimplemented methods. + rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty); +} + +// A simple service NOT implemented at servers so clients can test for +// that case. +service UnimplementedService { + // A call that no server should implement + rpc UnimplementedCall(grpc.testing.Empty) returns (grpc.testing.Empty); +} + +// A service used to control reconnect server. +service ReconnectService { + rpc Start(grpc.testing.ReconnectParams) returns (grpc.testing.Empty); + rpc Stop(grpc.testing.Empty) returns (grpc.testing.ReconnectInfo); +} diff --git a/src/Shared/Process/ProcessEx.cs b/src/Shared/Process/ProcessEx.cs new file mode 100644 index 000000000000..0a3092889b8d --- /dev/null +++ b/src/Shared/Process/ProcessEx.cs @@ -0,0 +1,210 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.InteropServices; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Internal; +using Xunit.Abstractions; + +namespace Microsoft.AspNetCore.Internal +{ + internal class ProcessEx : IDisposable + { + private static readonly string NUGET_PACKAGES = GetNugetPackagesRestorePath(); + + private readonly ITestOutputHelper _output; + private readonly Process _process; + private readonly StringBuilder _stderrCapture; + private readonly StringBuilder _stdoutCapture; + private readonly object _pipeCaptureLock = new object(); + private BlockingCollection _stdoutLines; + private TaskCompletionSource _exited; + private CancellationTokenSource _stdoutLinesCancellationSource = new CancellationTokenSource(TimeSpan.FromMinutes(5)); + + public ProcessEx(ITestOutputHelper output, Process proc) + { + _output = output; + _stdoutCapture = new StringBuilder(); + _stderrCapture = new StringBuilder(); + _stdoutLines = new BlockingCollection(); + + _process = proc; + proc.EnableRaisingEvents = true; + proc.OutputDataReceived += OnOutputData; + proc.ErrorDataReceived += OnErrorData; + proc.Exited += OnProcessExited; + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + + _exited = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + } + + public Task Exited => _exited.Task; + + public bool HasExited => _process.HasExited; + + public string Error + { + get + { + lock (_pipeCaptureLock) + { + return _stderrCapture.ToString(); + } + } + } + + public string Output + { + get + { + lock (_pipeCaptureLock) + { + return _stdoutCapture.ToString(); + } + } + } + + public IEnumerable OutputLinesAsEnumerable => _stdoutLines.GetConsumingEnumerable(_stdoutLinesCancellationSource.Token); + + public int ExitCode => _process.ExitCode; + + public object Id => _process.Id; + + public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary envVars = null) + { + var startInfo = new ProcessStartInfo(command, args) + { + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true, + WorkingDirectory = workingDirectory + }; + + if (envVars != null) + { + foreach (var envVar in envVars) + { + startInfo.EnvironmentVariables[envVar.Key] = envVar.Value; + } + } + + startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES; + + output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]"); + var proc = Process.Start(startInfo); + + return new ProcessEx(output, proc); + } + + public static ProcessEx RunViaShell(ITestOutputHelper output, string workingDirectory, string commandAndArgs) + { + var (shellExe, argsPrefix) = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) + ? ("cmd", "/c") + : ("bash", "-c"); + + var result = Run(output, workingDirectory, shellExe, $"{argsPrefix} \"{commandAndArgs}\""); + result.WaitForExit(assertSuccess: false); + return result; + } + + private void OnErrorData(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) + { + return; + } + + lock (_pipeCaptureLock) + { + _stderrCapture.AppendLine(e.Data); + } + + _output.WriteLine("[ERROR] " + e.Data); + } + + private void OnOutputData(object sender, DataReceivedEventArgs e) + { + if (e.Data == null) + { + return; + } + + lock (_pipeCaptureLock) + { + _stdoutCapture.AppendLine(e.Data); + } + + _output.WriteLine(e.Data); + + if (_stdoutLines != null) + { + _stdoutLines.Add(e.Data); + } + } + + private void OnProcessExited(object sender, EventArgs e) + { + _process.WaitForExit(); + _stdoutLines.CompleteAdding(); + _stdoutLines = null; + _exited.TrySetResult(_process.ExitCode); + } + + internal string GetFormattedOutput() + { + if (!_process.HasExited) + { + throw new InvalidOperationException("Process has not finished running."); + } + + return $"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"; + } + + public void WaitForExit(bool assertSuccess, TimeSpan? timeSpan = null) + { + if(!timeSpan.HasValue) + { + timeSpan = TimeSpan.FromSeconds(480); + } + + Exited.Wait(timeSpan.Value); + + if (assertSuccess && _process.ExitCode != 0) + { + throw new Exception($"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"); + } + } + + private static string GetNugetPackagesRestorePath() => + typeof(ProcessEx).Assembly + .GetCustomAttributes() + .First(attribute => attribute.Key == "TestPackageRestorePath") + .Value; + + public void Dispose() + { + if (_process != null && !_process.HasExited) + { + _process.KillTree(); + } + + _process.CancelOutputRead(); + _process.CancelErrorRead(); + + _process.ErrorDataReceived -= OnErrorData; + _process.OutputDataReceived -= OnOutputData; + _process.Exited -= OnProcessExited; + _process.Dispose(); + } + } +} From 22b836531af3fc4c257da57d373ebbca1c7d6667 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 11:09:25 -0800 Subject: [PATCH 02/29] Clean up --- .../test/InteropTests/{ => Infrastructure}/ClientProcess.cs | 2 +- .../InteropTests/{ => Infrastructure}/TaskExtensions.cs | 2 +- .../InteropTests/{ => Infrastructure}/WebServerProcess.cs | 4 +--- src/Grpc/test/InteropTests/InteropTests.csproj | 6 +++--- 4 files changed, 6 insertions(+), 8 deletions(-) rename src/Grpc/test/InteropTests/{ => Infrastructure}/ClientProcess.cs (97%) rename src/Grpc/test/InteropTests/{ => Infrastructure}/TaskExtensions.cs (98%) rename src/Grpc/test/InteropTests/{ => Infrastructure}/WebServerProcess.cs (96%) diff --git a/src/Grpc/test/InteropTests/ClientProcess.cs b/src/Grpc/test/InteropTests/Infrastructure/ClientProcess.cs similarity index 97% rename from src/Grpc/test/InteropTests/ClientProcess.cs rename to src/Grpc/test/InteropTests/Infrastructure/ClientProcess.cs index 72fbdc5d1953..fae1810f9b10 100644 --- a/src/Grpc/test/InteropTests/ClientProcess.cs +++ b/src/Grpc/test/InteropTests/Infrastructure/ClientProcess.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Internal; using Xunit.Abstractions; -namespace InteropTests +namespace InteropTests.Infrastructure { public class ClientProcess : IDisposable { diff --git a/src/Grpc/test/InteropTests/TaskExtensions.cs b/src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs similarity index 98% rename from src/Grpc/test/InteropTests/TaskExtensions.cs rename to src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs index da0ac5c00129..d6bba0110c57 100644 --- a/src/Grpc/test/InteropTests/TaskExtensions.cs +++ b/src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs @@ -7,7 +7,7 @@ using System.Threading; using System.Threading.Tasks; -namespace InteropTests +namespace InteropTests.Infrastructure { public static class TaskExtensions { diff --git a/src/Grpc/test/InteropTests/WebServerProcess.cs b/src/Grpc/test/InteropTests/Infrastructure/WebServerProcess.cs similarity index 96% rename from src/Grpc/test/InteropTests/WebServerProcess.cs rename to src/Grpc/test/InteropTests/Infrastructure/WebServerProcess.cs index 5a07b9c726f7..82242fc1c32b 100644 --- a/src/Grpc/test/InteropTests/WebServerProcess.cs +++ b/src/Grpc/test/InteropTests/Infrastructure/WebServerProcess.cs @@ -5,7 +5,7 @@ using Microsoft.AspNetCore.Internal; using Xunit.Abstractions; -namespace InteropTests +namespace InteropTests.Infrastructure { public class WebServerProcess : IDisposable { @@ -44,8 +44,6 @@ public Task WaitForReady() return _startTcs.Task; } - public Task Exited => _processEx.Exited; - private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) { var data = e.Data; diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj index b4acf820c565..686bcdd5b1c6 100644 --- a/src/Grpc/test/InteropTests/InteropTests.csproj +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -1,12 +1,12 @@ - + netcoreapp5.0 - - + + From d7e80d3611cede5efa216f045d82a58f20398a98 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 13:28:16 -0800 Subject: [PATCH 03/29] Move test project into build infrastructure --- src/Grpc/Directory.Build.props | 2 - src/Grpc/Directory.Build.targets | 2 - .../ClientProcess.cs | 6 +- .../{ => Helpers}/InteropTestsFixture.cs | 10 +-- .../WebServerProcess.cs | 5 +- .../Infrastructure/TaskExtensions.cs | 66 ------------------- src/Grpc/test/InteropTests/InteropTests.cs | 61 +++++++++-------- .../test/InteropTests/InteropTests.csproj | 13 +--- .../test/testassets/Directory.Build.props | 3 + .../test/testassets/Directory.Build.targets | 1 + .../InteropTestsClient.csproj | 2 +- .../InteropTestsWebsite.csproj | 2 +- 12 files changed, 57 insertions(+), 116 deletions(-) delete mode 100644 src/Grpc/Directory.Build.props delete mode 100644 src/Grpc/Directory.Build.targets rename src/Grpc/test/InteropTests/{Infrastructure => Helpers}/ClientProcess.cs (90%) rename src/Grpc/test/InteropTests/{ => Helpers}/InteropTestsFixture.cs (69%) rename src/Grpc/test/InteropTests/{Infrastructure => Helpers}/WebServerProcess.cs (90%) delete mode 100644 src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs create mode 100644 src/Grpc/test/testassets/Directory.Build.props create mode 100644 src/Grpc/test/testassets/Directory.Build.targets diff --git a/src/Grpc/Directory.Build.props b/src/Grpc/Directory.Build.props deleted file mode 100644 index 8c119d5413b5..000000000000 --- a/src/Grpc/Directory.Build.props +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Grpc/Directory.Build.targets b/src/Grpc/Directory.Build.targets deleted file mode 100644 index 8c119d5413b5..000000000000 --- a/src/Grpc/Directory.Build.targets +++ /dev/null @@ -1,2 +0,0 @@ - - diff --git a/src/Grpc/test/InteropTests/Infrastructure/ClientProcess.cs b/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs similarity index 90% rename from src/Grpc/test/InteropTests/Infrastructure/ClientProcess.cs rename to src/Grpc/test/InteropTests/Helpers/ClientProcess.cs index fae1810f9b10..052202b7e6a4 100644 --- a/src/Grpc/test/InteropTests/Infrastructure/ClientProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs @@ -1,11 +1,13 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using System.Diagnostics; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Internal; using Xunit.Abstractions; -namespace InteropTests.Infrastructure +namespace InteropTests.Helpers { public class ClientProcess : IDisposable { diff --git a/src/Grpc/test/InteropTests/InteropTestsFixture.cs b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs similarity index 69% rename from src/Grpc/test/InteropTests/InteropTestsFixture.cs rename to src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs index 97ad333799a6..9a510c7593c9 100644 --- a/src/Grpc/test/InteropTests/InteropTestsFixture.cs +++ b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs @@ -1,12 +1,14 @@ -using System; +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + +using System; using System.Threading.Tasks; using Xunit.Abstractions; -namespace InteropTests +namespace InteropTests.Helpers { public class InteropTestsFixture : IDisposable { - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); private WebServerProcess _process; public async Task EnsureStarted(ITestOutputHelper output) @@ -20,7 +22,7 @@ public async Task EnsureStarted(ITestOutputHelper output) _process = new WebServerProcess(webPath, output); - await _process.WaitForReady().TimeoutAfter(DefaultTimeout); + await _process.WaitForReady(); } public void Dispose() diff --git a/src/Grpc/test/InteropTests/Infrastructure/WebServerProcess.cs b/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs similarity index 90% rename from src/Grpc/test/InteropTests/Infrastructure/WebServerProcess.cs rename to src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs index 82242fc1c32b..83ce99f4443a 100644 --- a/src/Grpc/test/InteropTests/Infrastructure/WebServerProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs @@ -1,3 +1,6 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using System.Diagnostics; using System.Text; @@ -5,7 +8,7 @@ using Microsoft.AspNetCore.Internal; using Xunit.Abstractions; -namespace InteropTests.Infrastructure +namespace InteropTests.Helpers { public class WebServerProcess : IDisposable { diff --git a/src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs b/src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs deleted file mode 100644 index d6bba0110c57..000000000000 --- a/src/Grpc/test/InteropTests/Infrastructure/TaskExtensions.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Linq; -using System.Runtime.CompilerServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; - -namespace InteropTests.Infrastructure -{ - public static class TaskExtensions - { - public static async Task TimeoutAfter(this Task task, TimeSpan timeout, - [CallerFilePath] string filePath = null, - [CallerLineNumber] int lineNumber = default) - { - // Don't create a timer if the task is already completed - // or the debugger is attached - if (task.IsCompleted || Debugger.IsAttached) - { - return await task; - } - - var cts = new CancellationTokenSource(); - if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) - { - cts.Cancel(); - return await task; - } - else - { - throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); - } - } - - public static async Task TimeoutAfter(this Task task, TimeSpan timeout, - [CallerFilePath] string filePath = null, - [CallerLineNumber] int lineNumber = default) - { - // Don't create a timer if the task is already completed - // or the debugger is attached - if (task.IsCompleted || Debugger.IsAttached) - { - await task; - return; - } - - var cts = new CancellationTokenSource(); - if (task == await Task.WhenAny(task, Task.Delay(timeout, cts.Token))) - { - cts.Cancel(); - await task; - } - else - { - throw new TimeoutException(CreateMessage(timeout, filePath, lineNumber)); - } - } - - private static string CreateMessage(TimeSpan timeout, string filePath, int lineNumber) - => string.IsNullOrEmpty(filePath) - ? $"The operation timed out after reaching the limit of {timeout.TotalMilliseconds}ms." - : $"The operation at {filePath}:{lineNumber} timed out after reaching the limit of {timeout.TotalMilliseconds}ms."; - } -} diff --git a/src/Grpc/test/InteropTests/InteropTests.cs b/src/Grpc/test/InteropTests/InteropTests.cs index f271c394b062..bb6b6a4661e6 100644 --- a/src/Grpc/test/InteropTests/InteropTests.cs +++ b/src/Grpc/test/InteropTests/InteropTests.cs @@ -1,7 +1,12 @@ +// Copyright (c) .NET Foundation. All rights reserved. +// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. + using System; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; +using InteropTests.Helpers; +using Microsoft.AspNetCore.Testing; using Xunit; using Xunit.Abstractions; @@ -11,6 +16,34 @@ public class InteropTests : IClassFixture { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); + private readonly InteropTestsFixture _fixture; + private readonly ITestOutputHelper _output; + + public InteropTests(InteropTestsFixture fixture, ITestOutputHelper output) + { + _fixture = fixture; + _output = output; + } + + [Theory] + [MemberData(nameof(TestCaseData))] + public async Task InteropTestCase(string name) + { + await _fixture.EnsureStarted(_output).TimeoutAfter(DefaultTimeout); + + var clientPath = @"C:\Development\Source\AspNetCore\src\Grpc\test\testassets\InteropTestsClient\"; + + using (var clientProcess = new ClientProcess(_output, clientPath, 50052, name)) + { + await clientProcess.WaitForReady().TimeoutAfter(DefaultTimeout); + + await clientProcess.Exited.TimeoutAfter(DefaultTimeout); + + Assert.Equal(0, clientProcess.ExitCode); + } + } + + #region TestData // All interop test cases, minus GCE authentication specific tests private static string[] AllTests = new string[] { @@ -36,32 +69,6 @@ public class InteropTests : IClassFixture }; public static IEnumerable TestCaseData => AllTests.Select(t => new object[] { t }); - - private readonly ITestOutputHelper _output; - private readonly InteropTestsFixture _fixture; - - public InteropTests(ITestOutputHelper output, InteropTestsFixture fixture) - { - _output = output; - _fixture = fixture; - } - - [Theory] - [MemberData(nameof(TestCaseData))] - public async Task InteropTestCase(string name) - { - await _fixture.EnsureStarted(_output); - - var clientPath = @"C:\Development\Source\AspNetCore\src\Grpc\test\testassets\InteropTestsClient\"; - - using (var clientProcess = new ClientProcess(_output, clientPath, 50052, name)) - { - await clientProcess.WaitForReady().TimeoutAfter(DefaultTimeout); - - await clientProcess.Exited.TimeoutAfter(DefaultTimeout); - - Assert.Equal(0, clientProcess.ExitCode); - } - } + #endregion } } diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj index 686bcdd5b1c6..648382209153 100644 --- a/src/Grpc/test/InteropTests/InteropTests.csproj +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -1,19 +1,12 @@ - netcoreapp5.0 + $(DefaultNetCoreTargetFramework) - - - - - - - - - + + diff --git a/src/Grpc/test/testassets/Directory.Build.props b/src/Grpc/test/testassets/Directory.Build.props new file mode 100644 index 000000000000..a7dcd33665e5 --- /dev/null +++ b/src/Grpc/test/testassets/Directory.Build.props @@ -0,0 +1,3 @@ + + + diff --git a/src/Grpc/test/testassets/Directory.Build.targets b/src/Grpc/test/testassets/Directory.Build.targets new file mode 100644 index 000000000000..058246e40862 --- /dev/null +++ b/src/Grpc/test/testassets/Directory.Build.targets @@ -0,0 +1 @@ + diff --git a/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj b/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj index dafe7340b7da..da2b25244592 100644 --- a/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj +++ b/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj @@ -14,7 +14,7 @@ - + diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj b/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj index 6fd525dead53..be7bc717e46a 100644 --- a/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj +++ b/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj @@ -13,7 +13,7 @@ - + From 6e74c49d138f3450029b175db1d0a435714b79e4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 14:02:22 -0800 Subject: [PATCH 04/29] Clean up --- src/ProjectTemplates/test/Helpers/AspNetProcess.cs | 1 + src/ProjectTemplates/test/Helpers/ErrorMessages.cs | 2 ++ src/ProjectTemplates/test/Helpers/Project.cs | 1 + src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs | 1 + 4 files changed, 5 insertions(+) diff --git a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs index 753eb1258a52..86724b970fb9 100644 --- a/src/ProjectTemplates/test/Helpers/AspNetProcess.cs +++ b/src/ProjectTemplates/test/Helpers/AspNetProcess.cs @@ -11,6 +11,7 @@ using AngleSharp.Dom.Html; using AngleSharp.Parser.Html; using Microsoft.AspNetCore.Certificates.Generation; +using Microsoft.AspNetCore.Internal; using Microsoft.AspNetCore.Server.IntegrationTesting; using Microsoft.Extensions.CommandLineUtils; using Microsoft.Extensions.Logging.Abstractions; diff --git a/src/ProjectTemplates/test/Helpers/ErrorMessages.cs b/src/ProjectTemplates/test/Helpers/ErrorMessages.cs index c63f008f831a..744ada299bfb 100644 --- a/src/ProjectTemplates/test/Helpers/ErrorMessages.cs +++ b/src/ProjectTemplates/test/Helpers/ErrorMessages.cs @@ -1,6 +1,8 @@ // Copyright (c) .NET Foundation. All rights reserved. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. +using Microsoft.AspNetCore.Internal; + namespace Templates.Test.Helpers { internal static class ErrorMessages diff --git a/src/ProjectTemplates/test/Helpers/Project.cs b/src/ProjectTemplates/test/Helpers/Project.cs index 3fdcdf9d0ce2..b16801a5f748 100644 --- a/src/ProjectTemplates/test/Helpers/Project.cs +++ b/src/ProjectTemplates/test/Helpers/Project.cs @@ -10,6 +10,7 @@ using System.Runtime.InteropServices; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.CommandLineUtils; using Xunit; using Xunit.Abstractions; diff --git a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs index 3eed004ef60a..5c94fde2b49b 100644 --- a/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs +++ b/src/ProjectTemplates/test/Helpers/TemplatePackageInstaller.cs @@ -8,6 +8,7 @@ using System.Reflection; using System.Threading; using System.Threading.Tasks; +using Microsoft.AspNetCore.Internal; using Microsoft.Extensions.CommandLineUtils; using Xunit; using Xunit.Abstractions; From 63ba180920d55d49208f1166a459ac6a5275a1c8 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 16:04:34 -0800 Subject: [PATCH 05/29] Remove hardcoded paths --- .../InteropTests/Helpers/InteropTestsFixture.cs | 9 +++++++-- src/Grpc/test/InteropTests/InteropTests.cs | 11 ++++++++--- src/Grpc/test/InteropTests/InteropTests.csproj | 16 ++++++++++++++++ 3 files changed, 31 insertions(+), 5 deletions(-) diff --git a/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs index 9a510c7593c9..96932ae4267a 100644 --- a/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs +++ b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs @@ -11,6 +11,8 @@ public class InteropTestsFixture : IDisposable { private WebServerProcess _process; + public string Path { get; set; } + public async Task EnsureStarted(ITestOutputHelper output) { if (_process != null) @@ -18,9 +20,12 @@ public async Task EnsureStarted(ITestOutputHelper output) return; } - var webPath = @"C:\Development\Source\AspNetCore\src\Grpc\test\testassets\InteropTestsWebsite\"; + if (string.IsNullOrEmpty(Path)) + { + throw new InvalidOperationException("Path has not been set."); + } - _process = new WebServerProcess(webPath, output); + _process = new WebServerProcess(Path, output); await _process.WaitForReady(); } diff --git a/src/Grpc/test/InteropTests/InteropTests.cs b/src/Grpc/test/InteropTests/InteropTests.cs index bb6b6a4661e6..f5cbab65b9bf 100644 --- a/src/Grpc/test/InteropTests/InteropTests.cs +++ b/src/Grpc/test/InteropTests/InteropTests.cs @@ -4,6 +4,7 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; using System.Threading.Tasks; using InteropTests.Helpers; using Microsoft.AspNetCore.Testing; @@ -16,11 +17,17 @@ public class InteropTests : IClassFixture { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); + private readonly string _clientPath; private readonly InteropTestsFixture _fixture; private readonly ITestOutputHelper _output; public InteropTests(InteropTestsFixture fixture, ITestOutputHelper output) { + var attributes = typeof(InteropTests).Assembly.GetCustomAttributes().ToList(); + + fixture.Path = attributes.Single(a => a.Key == "InteropTestsWebsiteDir").Value; + _clientPath = attributes.Single(a => a.Key == "InteropTestsClientDir").Value; + _fixture = fixture; _output = output; } @@ -31,9 +38,7 @@ public async Task InteropTestCase(string name) { await _fixture.EnsureStarted(_output).TimeoutAfter(DefaultTimeout); - var clientPath = @"C:\Development\Source\AspNetCore\src\Grpc\test\testassets\InteropTestsClient\"; - - using (var clientProcess = new ClientProcess(_output, clientPath, 50052, name)) + using (var clientProcess = new ClientProcess(_output, _clientPath, 50052, name)) { await clientProcess.WaitForReady().TimeoutAfter(DefaultTimeout); diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj index 648382209153..e7df300f9e60 100644 --- a/src/Grpc/test/InteropTests/InteropTests.csproj +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -9,4 +9,20 @@ + + $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\src\Grpc\test\testassets + + + + + <_Parameter1>InteropTestsWebsiteDir + <_Parameter2>$(InteropTestsAssestsDir)\InteropTestsWebsite + + + + <_Parameter1>InteropTestsClientDir + <_Parameter2>$(InteropTestsAssestsDir)\InteropTestsClient + + + From a1b61b4126270498d81b91e999e5bc9d6c3a64b1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 16:12:01 -0800 Subject: [PATCH 06/29] Clean up --- src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs | 5 ----- src/Grpc/test/InteropTests/InteropTests.cs | 2 +- 2 files changed, 1 insertion(+), 6 deletions(-) diff --git a/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs b/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs index 83ce99f4443a..6f96eae5b618 100644 --- a/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs @@ -3,7 +3,6 @@ using System; using System.Diagnostics; -using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Internal; using Xunit.Abstractions; @@ -15,7 +14,6 @@ public class WebServerProcess : IDisposable private readonly Process _process; private readonly ProcessEx _processEx; private readonly TaskCompletionSource _startTcs; - private readonly StringBuilder _output; public WebServerProcess(string path, ITestOutputHelper output) { @@ -34,7 +32,6 @@ public WebServerProcess(string path, ITestOutputHelper output) _processEx = new ProcessEx(output, _process); _startTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - _output = new StringBuilder(); } public Task WaitForReady() @@ -52,8 +49,6 @@ private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) var data = e.Data; if (data != null) { - _output.AppendLine(data); - if (data.Contains("Application started.")) { _startTcs.TrySetResult(null); diff --git a/src/Grpc/test/InteropTests/InteropTests.cs b/src/Grpc/test/InteropTests/InteropTests.cs index f5cbab65b9bf..75e04e75bdef 100644 --- a/src/Grpc/test/InteropTests/InteropTests.cs +++ b/src/Grpc/test/InteropTests/InteropTests.cs @@ -15,7 +15,7 @@ namespace InteropTests { public class InteropTests : IClassFixture { - private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(15); + private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); private readonly string _clientPath; private readonly InteropTestsFixture _fixture; From 1c745e60cba6e684d1baf55d7ee1db6ccb55e28f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Tue, 12 Nov 2019 16:46:09 -0800 Subject: [PATCH 07/29] Fix build --- src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs index bd393b8d5865..d702e01be1e6 100644 --- a/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs +++ b/src/ProjectTemplates/test/SpaTemplateTest/SpaTemplateTestBase.cs @@ -10,6 +10,7 @@ using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.E2ETesting; +using Microsoft.AspNetCore.Internal; using Newtonsoft.Json.Linq; using OpenQA.Selenium; using Templates.Test.Helpers; From a892da0b6565c2374d8ff42bba4b80c9ef5b5ff4 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Wed, 13 Nov 2019 16:27:00 -0800 Subject: [PATCH 08/29] Add copyright notices --- THIRD-PARTY-NOTICES.txt | 17 ++++++++++++ .../testassets/InteropTestsWebsite/README.md | 27 ------------------- src/Grpc/test/testassets/README.md | 15 +++++++++++ 3 files changed, 32 insertions(+), 27 deletions(-) delete mode 100644 src/Grpc/test/testassets/InteropTestsWebsite/README.md create mode 100644 src/Grpc/test/testassets/README.md diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 99d1e377217f..39f41fdb0f0f 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -217,3 +217,20 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + +License notice for gRPC interop tests +------------------------------------- + +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. diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/README.md b/src/Grpc/test/testassets/InteropTestsWebsite/README.md deleted file mode 100644 index 91c7085da85e..000000000000 --- a/src/Grpc/test/testassets/InteropTestsWebsite/README.md +++ /dev/null @@ -1,27 +0,0 @@ -Running Grpc.Core interop client against Grpc.AspNetCore.Server interop server. -Context: https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md - -## Start the InteropTestsWebsite - -``` -# From this directory -$ dotnet run -Now listening on: http://localhost:50052 -``` - -## Build gRPC C# as a developer: -Follow https://github.com/grpc/grpc/tree/master/src/csharp -``` -python tools/run_tests/run_tests.py -l csharp -c dbg --build_only -``` - -## Running the interop client - -``` -cd src/csharp/Grpc.IntegrationTesting.Client/bin/Debug/net45 - -mono Grpc.IntegrationTesting.Client.exe --server_host=localhost --server_port=50052 --test_case=large_unary -``` - -NOTE: Currently the some tests will fail because not all the features are implemented -by Grpc.AspNetCore.Server diff --git a/src/Grpc/test/testassets/README.md b/src/Grpc/test/testassets/README.md new file mode 100644 index 000000000000..81fe95999081 --- /dev/null +++ b/src/Grpc/test/testassets/README.md @@ -0,0 +1,15 @@ +InteropTestsClient and InteropTestsWebsite are from https://github.com/grpc/grpc-dotnet/tree/master/testassets + +> 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. From cae017ffb1a03feed809821c635ca26057447f32 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 15 Nov 2019 11:20:35 -0800 Subject: [PATCH 09/29] Update azure template --- .azure/pipelines/ci.yml | 14 ++++++++------ src/Grpc/build.cmd | 3 +++ src/Grpc/build.sh | 7 +++++++ 3 files changed, 18 insertions(+), 6 deletions(-) create mode 100644 src/Grpc/build.cmd create mode 100644 src/Grpc/build.sh diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 9b491337a970..206e0b464fb3 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -557,8 +557,8 @@ stages: - template: jobs/default-build.yml parameters: condition: ne(variables['SkipTests'], 'true') - jobName: Windows_Templates_Test - jobDisplayName: "Test: Templates - Windows Server 2016 x64" + jobName: Windows_Integration_Test + jobDisplayName: "Test: Integration - Windows Server 2016 x64" agentOs: Windows isTestingJob: true steps: @@ -576,16 +576,18 @@ stages: displayName: Pack Templates - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" displayName: Test Templates + - script: ./src/Grpc/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/bl:artifacts/log/grpc.test.binlog" + displayName: Test gRPC interop artifacts: - - name: Windows_Test_Templates_Dumps + - name: Windows_Test_Integration_Dumps path: artifacts/dumps/ publishOnError: true includeForks: true - - name: Windows_Test_Templates_Logs + - name: Windows_Test_Integration_Logs path: artifacts/log/ publishOnError: true includeForks: true - - name: Windows_Test_Templates_Results + - name: Windows_Test_Integration_Results path: artifacts/TestResults/ publishOnError: true includeForks: true @@ -801,7 +803,7 @@ stages: - Linux_Test - MacOS_Test - Source_Build - - Windows_Templates_Test + - Windows_Integration_Test - Windows_Test pool: vmImage: vs2017-win2016 diff --git a/src/Grpc/build.cmd b/src/Grpc/build.cmd new file mode 100644 index 000000000000..55e26b75e6d4 --- /dev/null +++ b/src/Grpc/build.cmd @@ -0,0 +1,3 @@ +@ECHO OFF +SET RepoRoot=%~dp0..\.. +%RepoRoot%\build.cmd -projects %~dp0*\*.*proj %* diff --git a/src/Grpc/build.sh b/src/Grpc/build.sh new file mode 100644 index 000000000000..7046bb98a0fc --- /dev/null +++ b/src/Grpc/build.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash + +set -euo pipefail + +DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" +repo_root="$DIR/../.." +"$repo_root/build.sh" --projects "$DIR/**/*.*proj" "$@" From ca876b14c1a4bd1a2758e1d2fd42e97281f60a1f Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 15 Nov 2019 12:53:22 -0800 Subject: [PATCH 10/29] Fix build --- THIRD-PARTY-NOTICES.txt | 17 ----------------- src/Grpc/THIRD-PARTY-NOTICES | 26 ++++++++++++++++++++++++++ src/Grpc/build.cmd | 2 +- src/Grpc/test/testassets/README.md | 16 ++-------------- 4 files changed, 29 insertions(+), 32 deletions(-) create mode 100644 src/Grpc/THIRD-PARTY-NOTICES diff --git a/THIRD-PARTY-NOTICES.txt b/THIRD-PARTY-NOTICES.txt index 39f41fdb0f0f..99d1e377217f 100644 --- a/THIRD-PARTY-NOTICES.txt +++ b/THIRD-PARTY-NOTICES.txt @@ -217,20 +217,3 @@ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. - -License notice for gRPC interop tests -------------------------------------- - -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. diff --git a/src/Grpc/THIRD-PARTY-NOTICES b/src/Grpc/THIRD-PARTY-NOTICES new file mode 100644 index 000000000000..6b4a863467d6 --- /dev/null +++ b/src/Grpc/THIRD-PARTY-NOTICES @@ -0,0 +1,26 @@ +.NET Core uses third-party libraries or other resources that may be +distributed under licenses different than the .NET Core software. + +In the event that we accidentally failed to list a required notice, please +bring it to our attention. Post an issue or email us: + + dotnet@microsoft.com + +The attached notices are provided for information only. + +License notice for gRPC interop tests +------------------------------------- + +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. diff --git a/src/Grpc/build.cmd b/src/Grpc/build.cmd index 55e26b75e6d4..2406296662e9 100644 --- a/src/Grpc/build.cmd +++ b/src/Grpc/build.cmd @@ -1,3 +1,3 @@ @ECHO OFF SET RepoRoot=%~dp0..\.. -%RepoRoot%\build.cmd -projects %~dp0*\*.*proj %* +%RepoRoot%\build.cmd -projects %~dp0**\*.*proj %* diff --git a/src/Grpc/test/testassets/README.md b/src/Grpc/test/testassets/README.md index 81fe95999081..d7798c92695d 100644 --- a/src/Grpc/test/testassets/README.md +++ b/src/Grpc/test/testassets/README.md @@ -1,15 +1,3 @@ -InteropTestsClient and InteropTestsWebsite are from https://github.com/grpc/grpc-dotnet/tree/master/testassets +InteropTestsClient and InteropTestsWebsite are copied from [grpc-dotnet](https://github.com/grpc/grpc-dotnet/tree/master/testassets). -> 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. +For more information about the interop tests, see the [interop tests specification](https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md). From e6bf3eecc62b63e802fd4166268843f22c2b60c1 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 15 Nov 2019 20:30:32 -0800 Subject: [PATCH 11/29] Fix build? --- src/Grpc/Grpc.sln | 26 +++++++++---------- .../Helpers/InteropTestsFixture.cs | 4 +-- ...{WebServerProcess.cs => WebsiteProcess.cs} | 4 +-- .../test/InteropTests/InteropTests.csproj | 4 +-- .../Assert.cs | 0 .../AsyncStreamExtensions.cs | 0 .../IChannelWrapper.cs | 0 .../InteropClient.cs | 0 .../InteropClient.csproj} | 0 .../Program.cs | 0 .../RunTests.ps1 | 0 .../AsyncStreamExtensions.cs | 0 .../InteropWebsite.csproj} | 0 .../Program.cs | 0 .../Startup.cs | 0 .../TestServiceImpl.cs | 0 16 files changed, 19 insertions(+), 19 deletions(-) rename src/Grpc/test/InteropTests/Helpers/{WebServerProcess.cs => WebsiteProcess.cs} (93%) rename src/Grpc/test/testassets/{InteropTestsClient => InteropClient}/Assert.cs (100%) rename src/Grpc/test/testassets/{InteropTestsClient => InteropClient}/AsyncStreamExtensions.cs (100%) rename src/Grpc/test/testassets/{InteropTestsClient => InteropClient}/IChannelWrapper.cs (100%) rename src/Grpc/test/testassets/{InteropTestsClient => InteropClient}/InteropClient.cs (100%) rename src/Grpc/test/testassets/{InteropTestsClient/InteropTestsClient.csproj => InteropClient/InteropClient.csproj} (100%) rename src/Grpc/test/testassets/{InteropTestsClient => InteropClient}/Program.cs (100%) rename src/Grpc/test/testassets/{InteropTestsClient => InteropClient}/RunTests.ps1 (100%) rename src/Grpc/test/testassets/{InteropTestsWebsite => InteropWebsite}/AsyncStreamExtensions.cs (100%) rename src/Grpc/test/testassets/{InteropTestsWebsite/InteropTestsWebsite.csproj => InteropWebsite/InteropWebsite.csproj} (100%) rename src/Grpc/test/testassets/{InteropTestsWebsite => InteropWebsite}/Program.cs (100%) rename src/Grpc/test/testassets/{InteropTestsWebsite => InteropWebsite}/Startup.cs (100%) rename src/Grpc/test/testassets/{InteropTestsWebsite => InteropWebsite}/TestServiceImpl.cs (100%) diff --git a/src/Grpc/Grpc.sln b/src/Grpc/Grpc.sln index 25211ccd538b..d1491ed3e35b 100644 --- a/src/Grpc/Grpc.sln +++ b/src/Grpc/Grpc.sln @@ -7,11 +7,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{0FFB3605-0 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "testassets", "testassets", "{F5841B0A-901A-448F-9CC5-4CB393CE86AF}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTestsWebsite", "test\testassets\InteropTestsWebsite\InteropTestsWebsite.csproj", "{CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTests", "test\InteropTests\InteropTests.csproj", "{90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropTestsClient", "test\testassets\InteropTestsClient\InteropTestsClient.csproj", "{285945D7-DF5A-456B-920A-AFA459D6A1B2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "test\testassets\InteropWebsite\InteropWebsite.csproj", "{3AB7E8E4-BA36-44CE-844E-39DB66E46D45}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "InteropTests", "test\InteropTests\InteropTests.csproj", "{90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropClient", "test\testassets\InteropClient\InteropClient.csproj", "{66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -19,26 +19,26 @@ Global Release|Any CPU = Release|Any CPU EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Debug|Any CPU.Build.0 = Debug|Any CPU - {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Release|Any CPU.ActiveCfg = Release|Any CPU - {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43}.Release|Any CPU.Build.0 = Release|Any CPU - {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Debug|Any CPU.Build.0 = Debug|Any CPU - {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Release|Any CPU.ActiveCfg = Release|Any CPU - {285945D7-DF5A-456B-920A-AFA459D6A1B2}.Release|Any CPU.Build.0 = Release|Any CPU {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Debug|Any CPU.Build.0 = Debug|Any CPU {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Release|Any CPU.ActiveCfg = Release|Any CPU {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}.Release|Any CPU.Build.0 = Release|Any CPU + {3AB7E8E4-BA36-44CE-844E-39DB66E46D45}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {3AB7E8E4-BA36-44CE-844E-39DB66E46D45}.Debug|Any CPU.Build.0 = Debug|Any CPU + {3AB7E8E4-BA36-44CE-844E-39DB66E46D45}.Release|Any CPU.ActiveCfg = Release|Any CPU + {3AB7E8E4-BA36-44CE-844E-39DB66E46D45}.Release|Any CPU.Build.0 = Release|Any CPU + {66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution - {CA7D30EE-CB85-4E38-ABB4-8126DB62FD43} = {F5841B0A-901A-448F-9CC5-4CB393CE86AF} - {285945D7-DF5A-456B-920A-AFA459D6A1B2} = {F5841B0A-901A-448F-9CC5-4CB393CE86AF} {90BF37E6-B3F1-4EFC-A233-8288D8B32DD2} = {0FFB3605-0203-450F-80C8-F82CA2E8269F} + {3AB7E8E4-BA36-44CE-844E-39DB66E46D45} = {F5841B0A-901A-448F-9CC5-4CB393CE86AF} + {66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F} = {F5841B0A-901A-448F-9CC5-4CB393CE86AF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {3CAE66FD-9A59-49C2-B133-1D599225259A} diff --git a/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs index 96932ae4267a..a523d08d3fa8 100644 --- a/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs +++ b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs @@ -9,7 +9,7 @@ namespace InteropTests.Helpers { public class InteropTestsFixture : IDisposable { - private WebServerProcess _process; + private WebsiteProcess _process; public string Path { get; set; } @@ -25,7 +25,7 @@ public async Task EnsureStarted(ITestOutputHelper output) throw new InvalidOperationException("Path has not been set."); } - _process = new WebServerProcess(Path, output); + _process = new WebsiteProcess(Path, output); await _process.WaitForReady(); } diff --git a/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs b/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs similarity index 93% rename from src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs rename to src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs index 6f96eae5b618..723f6d1fba77 100644 --- a/src/Grpc/test/InteropTests/Helpers/WebServerProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs @@ -9,13 +9,13 @@ namespace InteropTests.Helpers { - public class WebServerProcess : IDisposable + public class WebsiteProcess : IDisposable { private readonly Process _process; private readonly ProcessEx _processEx; private readonly TaskCompletionSource _startTcs; - public WebServerProcess(string path, ITestOutputHelper output) + public WebsiteProcess(string path, ITestOutputHelper output) { _process = new Process(); _process.StartInfo = new ProcessStartInfo diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj index e7df300f9e60..ea2aa8c9f663 100644 --- a/src/Grpc/test/InteropTests/InteropTests.csproj +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -16,12 +16,12 @@ <_Parameter1>InteropTestsWebsiteDir - <_Parameter2>$(InteropTestsAssestsDir)\InteropTestsWebsite + <_Parameter2>$(InteropTestsAssestsDir)\InteropWebsite <_Parameter1>InteropTestsClientDir - <_Parameter2>$(InteropTestsAssestsDir)\InteropTestsClient + <_Parameter2>$(InteropTestsAssestsDir)\InteropClient diff --git a/src/Grpc/test/testassets/InteropTestsClient/Assert.cs b/src/Grpc/test/testassets/InteropClient/Assert.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/Assert.cs rename to src/Grpc/test/testassets/InteropClient/Assert.cs diff --git a/src/Grpc/test/testassets/InteropTestsClient/AsyncStreamExtensions.cs b/src/Grpc/test/testassets/InteropClient/AsyncStreamExtensions.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/AsyncStreamExtensions.cs rename to src/Grpc/test/testassets/InteropClient/AsyncStreamExtensions.cs diff --git a/src/Grpc/test/testassets/InteropTestsClient/IChannelWrapper.cs b/src/Grpc/test/testassets/InteropClient/IChannelWrapper.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/IChannelWrapper.cs rename to src/Grpc/test/testassets/InteropClient/IChannelWrapper.cs diff --git a/src/Grpc/test/testassets/InteropTestsClient/InteropClient.cs b/src/Grpc/test/testassets/InteropClient/InteropClient.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/InteropClient.cs rename to src/Grpc/test/testassets/InteropClient/InteropClient.cs diff --git a/src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/InteropTestsClient.csproj rename to src/Grpc/test/testassets/InteropClient/InteropClient.csproj diff --git a/src/Grpc/test/testassets/InteropTestsClient/Program.cs b/src/Grpc/test/testassets/InteropClient/Program.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/Program.cs rename to src/Grpc/test/testassets/InteropClient/Program.cs diff --git a/src/Grpc/test/testassets/InteropTestsClient/RunTests.ps1 b/src/Grpc/test/testassets/InteropClient/RunTests.ps1 similarity index 100% rename from src/Grpc/test/testassets/InteropTestsClient/RunTests.ps1 rename to src/Grpc/test/testassets/InteropClient/RunTests.ps1 diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/AsyncStreamExtensions.cs b/src/Grpc/test/testassets/InteropWebsite/AsyncStreamExtensions.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsWebsite/AsyncStreamExtensions.cs rename to src/Grpc/test/testassets/InteropWebsite/AsyncStreamExtensions.cs diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj b/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj similarity index 100% rename from src/Grpc/test/testassets/InteropTestsWebsite/InteropTestsWebsite.csproj rename to src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/Program.cs b/src/Grpc/test/testassets/InteropWebsite/Program.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsWebsite/Program.cs rename to src/Grpc/test/testassets/InteropWebsite/Program.cs diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/Startup.cs b/src/Grpc/test/testassets/InteropWebsite/Startup.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsWebsite/Startup.cs rename to src/Grpc/test/testassets/InteropWebsite/Startup.cs diff --git a/src/Grpc/test/testassets/InteropTestsWebsite/TestServiceImpl.cs b/src/Grpc/test/testassets/InteropWebsite/TestServiceImpl.cs similarity index 100% rename from src/Grpc/test/testassets/InteropTestsWebsite/TestServiceImpl.cs rename to src/Grpc/test/testassets/InteropWebsite/TestServiceImpl.cs From 59c451ce48499c3b35430d43be78cf8b74c95a19 Mon Sep 17 00:00:00 2001 From: James Newton-King Date: Fri, 15 Nov 2019 22:03:57 -0800 Subject: [PATCH 12/29] Fix build? --- .azure/pipelines/ci.yml | 2 +- src/Grpc/test/testassets/InteropClient/InteropClient.csproj | 1 + src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj | 1 + 3 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 206e0b464fb3..e27c8ec715d5 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -576,7 +576,7 @@ stages: displayName: Pack Templates - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" displayName: Test Templates - - script: ./src/Grpc/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/bl:artifacts/log/grpc.test.binlog" + - script: ./src/Grpc/build.cmd -ci -test "/bl:artifacts/log/grpc.test.binlog" displayName: Test gRPC interop artifacts: - name: Windows_Test_Integration_Dumps diff --git a/src/Grpc/test/testassets/InteropClient/InteropClient.csproj b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj index da2b25244592..4028de61a139 100644 --- a/src/Grpc/test/testassets/InteropClient/InteropClient.csproj +++ b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj @@ -5,6 +5,7 @@ netcoreapp5.0 enable 8.0 + false diff --git a/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj b/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj index be7bc717e46a..2b761bc95a0a 100644 --- a/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj +++ b/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj @@ -6,6 +6,7 @@ false enable 8.0 + false From 47dd2b6bcfabb618c9b282a896a0e91be8831f15 Mon Sep 17 00:00:00 2001 From: John Luo Date: Mon, 27 Jan 2020 17:45:36 -0800 Subject: [PATCH 13/29] Add gRPC interop tests to CI - Convert to using references managed by build infrastructure - Use produced AspNetCore.App shared framework - Save server logs - Dynamically bind to ports - Ensure InteropWebsite is built in the same configuration as the test project --- .azure/pipelines/ci.yml | 5 ++-- eng/Dependencies.props | 7 +++++- eng/Versions.props | 6 ++++- .../InteropTests/Helpers/ClientProcess.cs | 4 ++-- .../Helpers/InteropTestsFixture.cs | 6 +++++ .../InteropTests/Helpers/WebsiteProcess.cs | 23 +++++++++++++++++- src/Grpc/test/InteropTests/InteropTests.cs | 5 ++-- .../test/InteropTests/InteropTests.csproj | 17 +++++++++++-- .../test/testassets/Directory.Build.props | 6 ++++- .../test/testassets/Directory.Build.targets | 1 - .../InteropClient/InteropClient.csproj | 18 +++++++------- .../InteropWebsite/Directory.Build.targets | 24 +++++++++++++++++++ .../InteropWebsite/InteropWebsite.csproj | 7 +++--- .../test/testassets/InteropWebsite/Program.cs | 8 +++---- 14 files changed, 105 insertions(+), 32 deletions(-) delete mode 100644 src/Grpc/test/testassets/Directory.Build.targets create mode 100644 src/Grpc/test/testassets/InteropWebsite/Directory.Build.targets diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index e27c8ec715d5..6d6e47de22c8 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -574,8 +574,9 @@ stages: displayName: Build Repo - script: ./src/ProjectTemplates/build.cmd -ci -pack -NoRestore -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.pack.binlog" displayName: Pack Templates - - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" - displayName: Test Templates + # TEMP: disabling for faster testing + # - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" + # displayName: Test Templates - script: ./src/Grpc/build.cmd -ci -test "/bl:artifacts/log/grpc.test.binlog" displayName: Test gRPC interop artifacts: diff --git a/eng/Dependencies.props b/eng/Dependencies.props index ecee11a86fc0..dff681a3efc8 100644 --- a/eng/Dependencies.props +++ b/eng/Dependencies.props @@ -153,8 +153,13 @@ and are generated based on the last package release. + - + + + + + diff --git a/eng/Versions.props b/eng/Versions.props index aad2efaa684a..0a9a7e88c9e2 100644 --- a/eng/Versions.props +++ b/eng/Versions.props @@ -217,9 +217,13 @@ 0.9.9 0.12.0 4.2.1 + 2.3.0 4.2.1 - 3.8.0 + 3.10.0 2.27.0 + 2.27.0 + 2.27.0 + 2.27.0 3.0.0 3.0.0 3.0.0 diff --git a/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs b/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs index 052202b7e6a4..19c9c059f9bd 100644 --- a/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs @@ -15,7 +15,7 @@ public class ClientProcess : IDisposable private readonly ProcessEx _processEx; private readonly TaskCompletionSource _startTcs; - public ClientProcess(ITestOutputHelper output, string path, int port, string testCase) + public ClientProcess(ITestOutputHelper output, string path, string serverPort, string testCase) { _process = new Process(); _process.StartInfo = new ProcessStartInfo @@ -23,7 +23,7 @@ public ClientProcess(ITestOutputHelper output, string path, int port, string tes RedirectStandardOutput = true, RedirectStandardError = true, FileName = "dotnet.exe", - Arguments = @$"run -p {path} --use_tls false --server_port {port} --client_type httpclient --test_case {testCase}" + Arguments = @$"run -p {path} --use_tls false --server_port {serverPort} --client_type httpclient --test_case {testCase}" }; _process.EnableRaisingEvents = true; _process.OutputDataReceived += Process_OutputDataReceived; diff --git a/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs index a523d08d3fa8..86ffff4a0794 100644 --- a/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs +++ b/src/Grpc/test/InteropTests/Helpers/InteropTestsFixture.cs @@ -13,6 +13,10 @@ public class InteropTestsFixture : IDisposable public string Path { get; set; } + + public string ServerPort { get; private set; } + + public async Task EnsureStarted(ITestOutputHelper output) { if (_process != null) @@ -28,6 +32,8 @@ public async Task EnsureStarted(ITestOutputHelper output) _process = new WebsiteProcess(Path, output); await _process.WaitForReady(); + + ServerPort = _process.ServerPort; } public void Dispose() diff --git a/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs b/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs index 723f6d1fba77..05ebfcf215fd 100644 --- a/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs @@ -3,6 +3,11 @@ using System; using System.Diagnostics; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; using System.Threading.Tasks; using Microsoft.AspNetCore.Internal; using Xunit.Abstractions; @@ -14,16 +19,24 @@ public class WebsiteProcess : IDisposable private readonly Process _process; private readonly ProcessEx _processEx; private readonly TaskCompletionSource _startTcs; + private readonly StringBuilder _consoleOut = new StringBuilder(); + private readonly string _serverLogPath; + private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: .*:(?\d*)$"); + + public string ServerPort { get; private set; } public WebsiteProcess(string path, ITestOutputHelper output) { + var attributes = Assembly.GetExecutingAssembly() + .GetCustomAttributes(); + _serverLogPath = attributes.Single(a => a.Key == "ServerLogPath").Value; _process = new Process(); _process.StartInfo = new ProcessStartInfo { RedirectStandardOutput = true, RedirectStandardError = true, FileName = "dotnet.exe", - Arguments = @$"run -p {path}" + Arguments = $"run --no-build -p {path} -c {attributes.Single(a => a.Key == "Configuration").Value}" }; _process.EnableRaisingEvents = true; _process.OutputDataReceived += Process_OutputDataReceived; @@ -49,6 +62,13 @@ private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) var data = e.Data; if (data != null) { + _consoleOut.AppendLine(data); + var m = NowListeningRegex.Match(data); + if (m.Success) + { + ServerPort = m.Groups["port"].Value; + } + if (data.Contains("Application started.")) { _startTcs.TrySetResult(null); @@ -58,6 +78,7 @@ private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e) public void Dispose() { + File.WriteAllText(_serverLogPath, _consoleOut.ToString()); _processEx.Dispose(); } } diff --git a/src/Grpc/test/InteropTests/InteropTests.cs b/src/Grpc/test/InteropTests/InteropTests.cs index 75e04e75bdef..049d2ab66f7e 100644 --- a/src/Grpc/test/InteropTests/InteropTests.cs +++ b/src/Grpc/test/InteropTests/InteropTests.cs @@ -23,7 +23,8 @@ public class InteropTests : IClassFixture public InteropTests(InteropTestsFixture fixture, ITestOutputHelper output) { - var attributes = typeof(InteropTests).Assembly.GetCustomAttributes().ToList(); + var attributes = Assembly.GetExecutingAssembly() + .GetCustomAttributes(); fixture.Path = attributes.Single(a => a.Key == "InteropTestsWebsiteDir").Value; _clientPath = attributes.Single(a => a.Key == "InteropTestsClientDir").Value; @@ -38,7 +39,7 @@ public async Task InteropTestCase(string name) { await _fixture.EnsureStarted(_output).TimeoutAfter(DefaultTimeout); - using (var clientProcess = new ClientProcess(_output, _clientPath, 50052, name)) + using (var clientProcess = new ClientProcess(_output, _clientPath, _fixture.ServerPort, name)) { await clientProcess.WaitForReady().TimeoutAfter(DefaultTimeout); diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj index ea2aa8c9f663..7be2d4cc4e48 100644 --- a/src/Grpc/test/InteropTests/InteropTests.csproj +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -10,7 +10,7 @@ - $([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\, Directory.Build.props))\src\Grpc\test\testassets + $(RepoRoot)src\Grpc\test\testassets @@ -18,11 +18,24 @@ <_Parameter1>InteropTestsWebsiteDir <_Parameter2>$(InteropTestsAssestsDir)\InteropWebsite - + <_Parameter1>InteropTestsClientDir <_Parameter2>$(InteropTestsAssestsDir)\InteropClient + + + <_Parameter1>ServerLogPath + <_Parameter2>$(ArtifactsLogDir)InteropServer.log + + + + <_Parameter1>Configuration + <_Parameter2>$(Configuration) + + + + diff --git a/src/Grpc/test/testassets/Directory.Build.props b/src/Grpc/test/testassets/Directory.Build.props index a7dcd33665e5..16d5bfcfea26 100644 --- a/src/Grpc/test/testassets/Directory.Build.props +++ b/src/Grpc/test/testassets/Directory.Build.props @@ -1,3 +1,7 @@ - + + true + + + diff --git a/src/Grpc/test/testassets/Directory.Build.targets b/src/Grpc/test/testassets/Directory.Build.targets deleted file mode 100644 index 058246e40862..000000000000 --- a/src/Grpc/test/testassets/Directory.Build.targets +++ /dev/null @@ -1 +0,0 @@ - diff --git a/src/Grpc/test/testassets/InteropClient/InteropClient.csproj b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj index 4028de61a139..4f202cdee4df 100644 --- a/src/Grpc/test/testassets/InteropClient/InteropClient.csproj +++ b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj @@ -2,10 +2,9 @@ Exe - netcoreapp5.0 + $(DefaultNetCoreTargetFramework) enable 8.0 - false @@ -13,14 +12,13 @@ - - - - - - - - + + + + + + + diff --git a/src/Grpc/test/testassets/InteropWebsite/Directory.Build.targets b/src/Grpc/test/testassets/InteropWebsite/Directory.Build.targets new file mode 100644 index 000000000000..2f889b40eae9 --- /dev/null +++ b/src/Grpc/test/testassets/InteropWebsite/Directory.Build.targets @@ -0,0 +1,24 @@ + + + + + + $(RestoreAdditionalProjectSources);$(ArtifactsShippingPackagesDir) + $(TargetingPackVersion) + $(AspNetCoreBaselineVersion) + + + + + + + diff --git a/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj b/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj index 2b761bc95a0a..4b8d6d38509f 100644 --- a/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj +++ b/src/Grpc/test/testassets/InteropWebsite/InteropWebsite.csproj @@ -1,12 +1,11 @@ - + - netcoreapp5.0 + $(DefaultNetCoreTargetFramework) InProcess false enable 8.0 - false @@ -14,7 +13,7 @@ - + diff --git a/src/Grpc/test/testassets/InteropWebsite/Program.cs b/src/Grpc/test/testassets/InteropWebsite/Program.cs index b841b3528b52..fffaf75c559e 100644 --- a/src/Grpc/test/testassets/InteropWebsite/Program.cs +++ b/src/Grpc/test/testassets/InteropWebsite/Program.cs @@ -1,4 +1,4 @@ -#region Copyright notice and license +#region Copyright notice and license // Copyright 2019 The gRPC Authors // @@ -18,7 +18,6 @@ using System; using System.IO; -using Microsoft.AspNetCore; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Server.Kestrel.Core; using Microsoft.Extensions.Configuration; @@ -41,11 +40,10 @@ public static IHostBuilder CreateHostBuilder(string[] args) => { // Support --port and --use_tls cmdline arguments normally supported // by gRPC interop servers. - var port = context.Configuration.GetValue("port", 50052); - var useTls = context.Configuration.GetValue("use_tls", false); + var useTls = context.Configuration.GetValue("use_tls", false); options.Limits.MinRequestBodyDataRate = null; - options.ListenAnyIP(port, listenOptions => + options.ListenAnyIP(0, listenOptions => { Console.WriteLine($"Enabling connection encryption: {useTls}"); From e52af669ba87a8b57d009282dce5ea5ef9063d13 Mon Sep 17 00:00:00 2001 From: John Luo Date: Sun, 2 Feb 2020 19:30:16 -0800 Subject: [PATCH 14/29] Cleanup --- .azure/pipelines/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index 6d6e47de22c8..e27c8ec715d5 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -574,9 +574,8 @@ stages: displayName: Build Repo - script: ./src/ProjectTemplates/build.cmd -ci -pack -NoRestore -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.pack.binlog" displayName: Pack Templates - # TEMP: disabling for faster testing - # - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" - # displayName: Test Templates + - script: ./src/ProjectTemplates/build.cmd -ci -test -NoRestore -NoBuild -NoBuilddeps "/p:RunTemplateTests=true /bl:artifacts/log/template.test.binlog" + displayName: Test Templates - script: ./src/Grpc/build.cmd -ci -test "/bl:artifacts/log/grpc.test.binlog" displayName: Test gRPC interop artifacts: From 3a83d67a45b016e476e810e006525f99aed67723 Mon Sep 17 00:00:00 2001 From: John Luo Date: Sun, 15 Mar 2020 23:18:41 -0700 Subject: [PATCH 15/29] Rebase fix --- .../test/Helpers/ProcessEx.cs | 221 ------------------ 1 file changed, 221 deletions(-) delete mode 100644 src/ProjectTemplates/test/Helpers/ProcessEx.cs diff --git a/src/ProjectTemplates/test/Helpers/ProcessEx.cs b/src/ProjectTemplates/test/Helpers/ProcessEx.cs deleted file mode 100644 index db132bdafe07..000000000000 --- a/src/ProjectTemplates/test/Helpers/ProcessEx.cs +++ /dev/null @@ -1,221 +0,0 @@ -// Copyright (c) .NET Foundation. All rights reserved. -// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. - -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Diagnostics; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Runtime.InteropServices; -using System.Text; -using System.Threading; -using System.Threading.Tasks; -using Microsoft.Extensions.Internal; -using Xunit.Abstractions; - -namespace Templates.Test.Helpers -{ - internal class ProcessEx : IDisposable - { - private static readonly string NUGET_PACKAGES = GetNugetPackagesRestorePath(); - - private readonly ITestOutputHelper _output; - private readonly Process _process; - private readonly StringBuilder _stderrCapture; - private readonly StringBuilder _stdoutCapture; - private readonly object _pipeCaptureLock = new object(); - private BlockingCollection _stdoutLines; - private TaskCompletionSource _exited; - private CancellationTokenSource _stdoutLinesCancellationSource = new CancellationTokenSource(TimeSpan.FromMinutes(5)); - - public ProcessEx(ITestOutputHelper output, Process proc) - { - _output = output; - _stdoutCapture = new StringBuilder(); - _stderrCapture = new StringBuilder(); - _stdoutLines = new BlockingCollection(); - - _process = proc; - proc.EnableRaisingEvents = true; - proc.OutputDataReceived += OnOutputData; - proc.ErrorDataReceived += OnErrorData; - proc.Exited += OnProcessExited; - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - - _exited = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); - } - - public Task Exited => _exited.Task; - - public bool HasExited => _process.HasExited; - - public string Error - { - get - { - lock (_pipeCaptureLock) - { - return _stderrCapture.ToString(); - } - } - } - - public string Output - { - get - { - lock (_pipeCaptureLock) - { - return _stdoutCapture.ToString(); - } - } - } - - public IEnumerable OutputLinesAsEnumerable => _stdoutLines.GetConsumingEnumerable(_stdoutLinesCancellationSource.Token); - - public int ExitCode => _process.ExitCode; - - public object Id => _process.Id; - - public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, string command, string args = null, IDictionary envVars = null) - { - var startInfo = new ProcessStartInfo(command, args) - { - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true, - WorkingDirectory = workingDirectory - }; - - if (envVars != null) - { - foreach (var envVar in envVars) - { - startInfo.EnvironmentVariables[envVar.Key] = envVar.Value; - } - } - - startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES; - - if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) - { - startInfo.EnvironmentVariables["NUGET_FALLBACK_PACKAGES"] = Environment.GetEnvironmentVariable("NUGET_FALLBACK_PACKAGES"); - } - - output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]"); - var proc = Process.Start(startInfo); - - return new ProcessEx(output, proc); - } - - public static ProcessEx RunViaShell(ITestOutputHelper output, string workingDirectory, string commandAndArgs) - { - var (shellExe, argsPrefix) = RuntimeInformation.IsOSPlatform(OSPlatform.Windows) - ? ("cmd", "/c") - : ("bash", "-c"); - - var result = Run(output, workingDirectory, shellExe, $"{argsPrefix} \"{commandAndArgs}\""); - result.WaitForExit(assertSuccess: false); - return result; - } - - private void OnErrorData(object sender, DataReceivedEventArgs e) - { - if (e.Data == null) - { - return; - } - - lock (_pipeCaptureLock) - { - _stderrCapture.AppendLine(e.Data); - } - - _output.WriteLine("[ERROR] " + e.Data); - } - - private void OnOutputData(object sender, DataReceivedEventArgs e) - { - if (e.Data == null) - { - return; - } - - lock (_pipeCaptureLock) - { - _stdoutCapture.AppendLine(e.Data); - } - - _output.WriteLine(e.Data); - - if (_stdoutLines != null) - { - _stdoutLines.Add(e.Data); - } - } - - private void OnProcessExited(object sender, EventArgs e) - { - _process.WaitForExit(); - _stdoutLines.CompleteAdding(); - _stdoutLines = null; - _exited.TrySetResult(_process.ExitCode); - } - - internal string GetFormattedOutput() - { - if (!_process.HasExited) - { - throw new InvalidOperationException($"Process {_process.ProcessName} with pid: {_process.Id} has not finished running."); - } - - return $"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"; - } - - public void WaitForExit(bool assertSuccess, TimeSpan? timeSpan = null) - { - if(!timeSpan.HasValue) - { - timeSpan = TimeSpan.FromSeconds(600); - } - - var exited = Exited.Wait(timeSpan.Value); - if (!exited) - { - _output.WriteLine($"The process didn't exit within the allotted time ({timeSpan.Value.TotalSeconds} seconds)."); - _process.Dispose(); - } - else if (assertSuccess && _process.ExitCode != 0) - { - throw new Exception($"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"); - } - } - - private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE"))) - ? typeof(ProcessEx).Assembly - .GetCustomAttributes() - .First(attribute => attribute.Key == "TestPackageRestorePath") - .Value - : Environment.GetEnvironmentVariable("NUGET_RESTORE"); - - public void Dispose() - { - if (_process != null && !_process.HasExited) - { - _process.KillTree(); - } - - _process.CancelOutputRead(); - _process.CancelErrorRead(); - - _process.ErrorDataReceived -= OnErrorData; - _process.OutputDataReceived -= OnOutputData; - _process.Exited -= OnProcessExited; - _process.Dispose(); - } - } -} From 71c0ab47eaa004eb6312367e1bfaec2881000b70 Mon Sep 17 00:00:00 2001 From: John Luo Date: Tue, 17 Mar 2020 02:28:38 -0700 Subject: [PATCH 16/29] Include tests assets in build directory for Helix --- Directory.Build.targets | 2 ++ eng/targets/FunctionalTestAsset.targets | 9 +++++++ eng/targets/FunctionalTestWithAssets.targets | 27 +++++++++++++++++++ .../InteropTests/Helpers/ClientProcess.cs | 2 +- .../InteropTests/Helpers/WebsiteProcess.cs | 2 +- src/Grpc/test/InteropTests/InteropTests.cs | 6 ++--- .../test/InteropTests/InteropTests.csproj | 26 +++--------------- .../test/testassets/Directory.Build.props | 7 ----- .../testassets/InteropClient/InteropClient.cs | 4 +-- .../InteropClient/InteropClient.csproj | 4 +-- 10 files changed, 50 insertions(+), 39 deletions(-) create mode 100644 eng/targets/FunctionalTestAsset.targets create mode 100644 eng/targets/FunctionalTestWithAssets.targets delete mode 100644 src/Grpc/test/testassets/Directory.Build.props diff --git a/Directory.Build.targets b/Directory.Build.targets index a62e3ed6a48b..9a30afbb8e09 100644 --- a/Directory.Build.targets +++ b/Directory.Build.targets @@ -167,5 +167,7 @@ + + diff --git a/eng/targets/FunctionalTestAsset.targets b/eng/targets/FunctionalTestAsset.targets new file mode 100644 index 000000000000..04b351334127 --- /dev/null +++ b/eng/targets/FunctionalTestAsset.targets @@ -0,0 +1,9 @@ + + + + + $(MSBuildProjectName)\%(ResolvedFileToPublish.RelativePath) + + + + \ No newline at end of file diff --git a/eng/targets/FunctionalTestWithAssets.targets b/eng/targets/FunctionalTestWithAssets.targets new file mode 100644 index 000000000000..b6194607cb43 --- /dev/null +++ b/eng/targets/FunctionalTestWithAssets.targets @@ -0,0 +1,27 @@ + + + + + + + + + + + + + + %(DependencyPayload.RelativePath) + PreserveNewest + PreserveNewest + + + + + \ No newline at end of file diff --git a/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs b/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs index 19c9c059f9bd..c196840e6b99 100644 --- a/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/ClientProcess.cs @@ -23,7 +23,7 @@ public ClientProcess(ITestOutputHelper output, string path, string serverPort, s RedirectStandardOutput = true, RedirectStandardError = true, FileName = "dotnet.exe", - Arguments = @$"run -p {path} --use_tls false --server_port {serverPort} --client_type httpclient --test_case {testCase}" + Arguments = @$"{path} --use_tls false --server_port {serverPort} --client_type httpclient --test_case {testCase}" }; _process.EnableRaisingEvents = true; _process.OutputDataReceived += Process_OutputDataReceived; diff --git a/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs b/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs index 05ebfcf215fd..d56a3a18cbfa 100644 --- a/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs +++ b/src/Grpc/test/InteropTests/Helpers/WebsiteProcess.cs @@ -36,7 +36,7 @@ public WebsiteProcess(string path, ITestOutputHelper output) RedirectStandardOutput = true, RedirectStandardError = true, FileName = "dotnet.exe", - Arguments = $"run --no-build -p {path} -c {attributes.Single(a => a.Key == "Configuration").Value}" + Arguments = path }; _process.EnableRaisingEvents = true; _process.OutputDataReceived += Process_OutputDataReceived; diff --git a/src/Grpc/test/InteropTests/InteropTests.cs b/src/Grpc/test/InteropTests/InteropTests.cs index 049d2ab66f7e..b12ec316dc67 100644 --- a/src/Grpc/test/InteropTests/InteropTests.cs +++ b/src/Grpc/test/InteropTests/InteropTests.cs @@ -3,6 +3,7 @@ using System; using System.Collections.Generic; +using System.IO; using System.Linq; using System.Reflection; using System.Threading.Tasks; @@ -17,7 +18,7 @@ public class InteropTests : IClassFixture { private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30); - private readonly string _clientPath; + private readonly string _clientPath = Path.Combine(Directory.GetCurrentDirectory(), "InteropClient", "InteropClient.dll"); private readonly InteropTestsFixture _fixture; private readonly ITestOutputHelper _output; @@ -26,8 +27,7 @@ public InteropTests(InteropTestsFixture fixture, ITestOutputHelper output) var attributes = Assembly.GetExecutingAssembly() .GetCustomAttributes(); - fixture.Path = attributes.Single(a => a.Key == "InteropTestsWebsiteDir").Value; - _clientPath = attributes.Single(a => a.Key == "InteropTestsClientDir").Value; + fixture.Path = Path.Combine(Directory.GetCurrentDirectory(), "InteropWebsite", "InteropWebsite.dll"); _fixture = fixture; _output = output; diff --git a/src/Grpc/test/InteropTests/InteropTests.csproj b/src/Grpc/test/InteropTests/InteropTests.csproj index 7be2d4cc4e48..a2ba26bcffeb 100644 --- a/src/Grpc/test/InteropTests/InteropTests.csproj +++ b/src/Grpc/test/InteropTests/InteropTests.csproj @@ -1,41 +1,21 @@ + true $(DefaultNetCoreTargetFramework) - - - - $(RepoRoot)src\Grpc\test\testassets - - - - - <_Parameter1>InteropTestsWebsiteDir - <_Parameter2>$(InteropTestsAssestsDir)\InteropWebsite - - - - <_Parameter1>InteropTestsClientDir - <_Parameter2>$(InteropTestsAssestsDir)\InteropClient - <_Parameter1>ServerLogPath <_Parameter2>$(ArtifactsLogDir)InteropServer.log - - <_Parameter1>Configuration - <_Parameter2>$(Configuration) - - - - + + diff --git a/src/Grpc/test/testassets/Directory.Build.props b/src/Grpc/test/testassets/Directory.Build.props deleted file mode 100644 index 16d5bfcfea26..000000000000 --- a/src/Grpc/test/testassets/Directory.Build.props +++ /dev/null @@ -1,7 +0,0 @@ - - - true - - - - diff --git a/src/Grpc/test/testassets/InteropClient/InteropClient.cs b/src/Grpc/test/testassets/InteropClient/InteropClient.cs index 5c77b621d8e9..27d51f699858 100644 --- a/src/Grpc/test/testassets/InteropClient/InteropClient.cs +++ b/src/Grpc/test/testassets/InteropClient/InteropClient.cs @@ -124,7 +124,7 @@ public static void Run(string[] args) using (var interopClient = new InteropClient(options)) { - interopClient.Run().Wait(); + interopClient.Run().GetAwaiter().GetResult(); } }); } @@ -158,7 +158,7 @@ private async Task HttpClientCreateChannel() { var pem = File.ReadAllText("Certs/ca.pem"); var certData = GetBytesFromPem(pem, "CERTIFICATE"); - var cert = new X509Certificate2(certData); + var cert = new X509Certificate2(certData!); httpClientHandler.ClientCertificates.Add(cert); } diff --git a/src/Grpc/test/testassets/InteropClient/InteropClient.csproj b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj index 4f202cdee4df..274295c83866 100644 --- a/src/Grpc/test/testassets/InteropClient/InteropClient.csproj +++ b/src/Grpc/test/testassets/InteropClient/InteropClient.csproj @@ -1,4 +1,4 @@ - + Exe @@ -14,7 +14,7 @@ - + From a3b9a96b0e26b4b234735b7e99337df6a8844b59 Mon Sep 17 00:00:00 2001 From: John Luo Date: Tue, 17 Mar 2020 15:33:20 -0700 Subject: [PATCH 17/29] Incorporate changes in ProcessEx --- src/Shared/Process/ProcessEx.cs | 27 +++++++++++++++++++-------- 1 file changed, 19 insertions(+), 8 deletions(-) diff --git a/src/Shared/Process/ProcessEx.cs b/src/Shared/Process/ProcessEx.cs index 0a3092889b8d..c1743a2f0a96 100644 --- a/src/Shared/Process/ProcessEx.cs +++ b/src/Shared/Process/ProcessEx.cs @@ -5,6 +5,7 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Diagnostics; +using System.IO; using System.Linq; using System.Reflection; using System.Runtime.InteropServices; @@ -100,6 +101,11 @@ public static ProcessEx Run(ITestOutputHelper output, string workingDirectory, s startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES; + if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix"))) + { + startInfo.EnvironmentVariables["NUGET_FALLBACK_PACKAGES"] = Environment.GetEnvironmentVariable("NUGET_FALLBACK_PACKAGES"); + } + output.WriteLine($"==> {startInfo.FileName} {startInfo.Arguments} [{startInfo.WorkingDirectory}]"); var proc = Process.Start(startInfo); @@ -164,7 +170,7 @@ internal string GetFormattedOutput() { if (!_process.HasExited) { - throw new InvalidOperationException("Process has not finished running."); + throw new InvalidOperationException($"Process {_process.ProcessName} with pid: {_process.Id} has not finished running."); } return $"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"; @@ -174,22 +180,27 @@ public void WaitForExit(bool assertSuccess, TimeSpan? timeSpan = null) { if(!timeSpan.HasValue) { - timeSpan = TimeSpan.FromSeconds(480); + timeSpan = TimeSpan.FromSeconds(600); } - Exited.Wait(timeSpan.Value); - - if (assertSuccess && _process.ExitCode != 0) + var exited = Exited.Wait(timeSpan.Value); + if (!exited) + { + _output.WriteLine($"The process didn't exit within the allotted time ({timeSpan.Value.TotalSeconds} seconds)."); + _process.Dispose(); + } + else if (assertSuccess && _process.ExitCode != 0) { throw new Exception($"Process exited with code {_process.ExitCode}\nStdErr: {Error}\nStdOut: {Output}"); } } - private static string GetNugetPackagesRestorePath() => - typeof(ProcessEx).Assembly + private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE"))) + ? typeof(ProcessEx).Assembly .GetCustomAttributes() .First(attribute => attribute.Key == "TestPackageRestorePath") - .Value; + .Value + : Environment.GetEnvironmentVariable("NUGET_RESTORE"); public void Dispose() { From 7e573e376d3a81c6ec2d24294ff41a50a8057974 Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 18 Mar 2020 14:58:08 -0700 Subject: [PATCH 18/29] Include Grpc test in regular build --- eng/Build.props | 1 + 1 file changed, 1 insertion(+) diff --git a/eng/Build.props b/eng/Build.props index 4c0542db15da..a1aa1f6e04f6 100644 --- a/eng/Build.props +++ b/eng/Build.props @@ -135,6 +135,7 @@ $(RepoRoot)src\Features\JsonPatch\**\*.*proj; $(RepoRoot)src\DataProtection\**\*.*proj; $(RepoRoot)src\Antiforgery\**\*.*proj; + $(RepoRoot)src\Grpc\**\*.*proj; $(RepoRoot)src\Hosting\**\*.*proj; $(RepoRoot)src\Http\**\*.*proj; $(RepoRoot)src\Html\**\*.*proj; From d5d4bea466736b61e5da1cced67523a576a6bbd9 Mon Sep 17 00:00:00 2001 From: John Luo Date: Wed, 18 Mar 2020 16:43:15 -0700 Subject: [PATCH 19/29] Fixup --- .azure/pipelines/ci.yml | 4 ++-- eng/Build.props | 8 +++++++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/.azure/pipelines/ci.yml b/.azure/pipelines/ci.yml index e27c8ec715d5..793f11b6e5ab 100644 --- a/.azure/pipelines/ci.yml +++ b/.azure/pipelines/ci.yml @@ -676,9 +676,9 @@ stages: # Build the shared framework - script: ./build.cmd -ci -all -pack -arch x64 -buildNative /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log /bl:artifacts/log/helix.build.x64.binlog displayName: Build shared fx - - script: .\restore.cmd -ci + - script: .\restore.cmd -ci /p:BuildInteropProjects=true displayName: Restore - - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl + - script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsRequiredCheck=true /p:IsHelixJob=true /p:BuildAllProjects=true /p:BuildInteropProjects=true /p:BuildNative=true /p:RunTemplateTests=true /p:ASPNETCORE_TEST_LOG_DIR=artifacts/log -bl displayName: Run build.cmd helix target env: HelixApiAccessToken: $(HelixApiAccessToken) # Needed for internal queues diff --git a/eng/Build.props b/eng/Build.props index a1aa1f6e04f6..6f1b3f190898 100644 --- a/eng/Build.props +++ b/eng/Build.props @@ -123,6 +123,13 @@ + + + + + +