Add gRPC interop tests (#17040)

* WIP add interop tests

* Clean up

* Move test project into build infrastructure

* Clean up

* Remove hardcoded paths

* Clean up

* Fix build

* Add copyright notices

* Update azure template

* Fix build

* Fix build?

* Fix build?

* 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

* Cleanup

* Rebase fix

* Include tests assets in build directory for Helix

* Incorporate changes in ProcessEx

* Include Grpc test in regular build

* Fixup

* Test

* exe doesn't always exist

* Capture logs on helix

* Maybe this will work

* There are two application started messages

* Derp

* Cleanup

* Update directory

* Add interop tests to more pipelines

* mkdir

Co-authored-by: John Luo <johluo@microsoft.com>
This commit is contained in:
James Newton-King 2020-03-21 15:32:45 +13:00 committed by GitHub
parent 91afecd630
commit 8afb78fb95
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
42 changed files with 2346 additions and 10 deletions

View File

@ -674,9 +674,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
@ -701,7 +701,7 @@ stages:
# Build the x86 shared framework
- script: .\restore.cmd -ci
displayName: Restore
- script: .\build.cmd -ci -NoRestore -test -projects eng\helix\helix.proj /p:IsHelixJob=true /p:IsHelixDaily=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:IsHelixJob=true /p:IsHelixDaily=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

View File

@ -35,7 +35,7 @@ jobs:
displayName: Build shared fx
- script: .\restore.cmd -ci
displayName: Restore
- script: .\build.cmd -ci -NoRestore -test -noBuildJava -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /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 -noBuildJava -projects eng\helix\helix.proj /p:RunQuarantinedTests=true /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

View File

@ -167,5 +167,7 @@
<Import Project="eng\targets\Npm.Common.targets" Condition="'$(MSBuildProjectExtension)' == '.npmproj'" />
<Import Project="eng\targets\ReferenceAssembly.targets" Condition=" $(HasReferenceAssembly) " />
<Import Project="eng\targets\Helix.targets" Condition="'$(IsTestProject)' == 'true'" />
<Import Project="eng\targets\FunctionalTestAsset.targets" Condition="'$(IsTestAssetProject)' == 'true'" />
<Import Project="eng\targets\FunctionalTestWithAssets.targets" Condition="'$(ContainsFunctionalTestAssets)' == 'true'" />
</Project>

View File

@ -123,6 +123,13 @@
<ProjectToBuild Condition=" '$(BuildJava)' == 'true'" Include="@(JavaProjects)" Exclude="@(ProjectToExclude)" />
<ProjectToExclude Condition=" '$(BuildJava)' != 'true'" Include="@(JavaProjects)" />
<!-- These interop test projects can only be restored and built after the shared framework is built -->
<InteropProjects Include="$(RepoRoot)src\Grpc\**\*.csproj"
Exclude="@(ProjectToExclude)" />
<ProjectToBuild Condition=" '$(BuildInteropProjects)' == 'true'" Include="@(InteropProjects)" Exclude="@(ProjectToExclude)" />
<ProjectToExclude Condition=" '$(BuildInteropProjects)' != 'true'" Include="@(InteropProjects)" />
<!--
Use caution to avoid deep recursion. If the globbing pattern picks up something which exceeds MAX_PATH,
the entire pattern will silently fail to evaluate correctly.

View File

@ -154,8 +154,13 @@ and are generated based on the last package release.
<ItemGroup Label="External dependencies" Condition="'$(DotNetBuildFromSource)' != 'true'">
<LatestPackageReference Include="AngleSharp" Version="$(AngleSharpPackageVersion)" />
<LatestPackageReference Include="BenchmarkDotNet" Version="$(BenchmarkDotNetPackageVersion)" />
<LatestPackageReference Include="CommandLineParser" Version="$(CommandLineParserPackageVersion)" />
<LatestPackageReference Include="FSharp.Core" Version="$(FSharpCorePackageVersion)" />
<LatestPackageReference Include="Google.ProtoBuf" Version="$(GoogleProtoBufPackageVersion)" />
<LatestPackageReference Include="Google.Protobuf" Version="$(GoogleProtobufPackageVersion)" />
<LatestPackageReference Include="Grpc.AspNetCore" Version="$(GrpcAspNetCorePackageVersion)" />
<LatestPackageReference Include="Grpc.Auth" Version="$(GrpcAuthPackageVersion)" />
<LatestPackageReference Include="Grpc.Net.Client" Version="$(GrpcNetClientPackageVersion)" />
<LatestPackageReference Include="Grpc.Tools" Version="$(GrpcToolsPackageVersion)" />
<LatestPackageReference Include="IdentityServer4" Version="$(IdentityServer4PackageVersion)" />
<LatestPackageReference Include="IdentityServer4.AspNetIdentity" Version="$(IdentityServer4AspNetIdentityPackageVersion)" />
<LatestPackageReference Include="IdentityServer4.EntityFramework" Version="$(IdentityServer4EntityFrameworkPackageVersion)" />

View File

@ -217,9 +217,13 @@
<AngleSharpPackageVersion>0.9.9</AngleSharpPackageVersion>
<BenchmarkDotNetPackageVersion>0.12.0</BenchmarkDotNetPackageVersion>
<CastleCorePackageVersion>4.2.1</CastleCorePackageVersion>
<CommandLineParserPackageVersion>2.3.0</CommandLineParserPackageVersion>
<FSharpCorePackageVersion>4.2.1</FSharpCorePackageVersion>
<GoogleProtobufPackageVersion>3.8.0</GoogleProtobufPackageVersion>
<GoogleProtobufPackageVersion>3.10.0</GoogleProtobufPackageVersion>
<GrpcAspNetCorePackageVersion>2.27.0</GrpcAspNetCorePackageVersion>
<GrpcAuthPackageVersion>2.27.0</GrpcAuthPackageVersion>
<GrpcNetClientPackageVersion>2.27.0</GrpcNetClientPackageVersion>
<GrpcToolsPackageVersion>2.27.0</GrpcToolsPackageVersion>
<IdentityServer4AspNetIdentityPackageVersion>3.0.0</IdentityServer4AspNetIdentityPackageVersion>
<IdentityServer4EntityFrameworkPackageVersion>3.0.0</IdentityServer4EntityFrameworkPackageVersion>
<IdentityServer4PackageVersion>3.0.0</IdentityServer4PackageVersion>

View File

@ -41,7 +41,7 @@ echo "Creating nugetRestore directory: $NUGET_RESTORE"
mkdir $NUGET_RESTORE
mkdir logs
ls -la
ls -laR
RESET="\033[0m"
RED="\033[0;31m"

View File

@ -0,0 +1,9 @@
<Project>
<Target Name="CollectFunctionalTestPayload" DependsOnTargets="Publish" Returns="@(DependencyPayload)" >
<ItemGroup>
<DependencyPayload Include="@(ResolvedFileToPublish)">
<RelativePath>$(MSBuildProjectName)\%(ResolvedFileToPublish.RelativePath)</RelativePath>
</DependencyPayload>
</ItemGroup>
</Target>
</Project>

View File

@ -0,0 +1,27 @@
<Project>
<ItemGroup>
<ProjectReference Include="@(FunctionalTestAssetProjectReference)" ReferenceOutputAssembly="false" />
</ItemGroup>
<Target Name="CollectTestAssetPayload" BeforeTargets="GetCopyToOutputDirectoryItems" >
<MSBuild Targets="CollectFunctionalTestPayload"
BuildInParallel="true"
SkipNonexistentTargets="true"
Projects="%(FunctionalTestAssetProjectReference.Identity)"
RebaseOutputs="True">
<Output TaskParameter="TargetOutputs" ItemName="DependencyPayload" />
</MSBuild>
<ItemGroup>
<ContentWithTargetPath
Include="@(DependencyPayload)"
Condition="'@(DependencyPayload->Count())' != '0'">
<TargetPath>%(DependencyPayload.RelativePath)</TargetPath>
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<CopyToPublishDirectory>PreserveNewest</CopyToPublishDirectory>
</ContentWithTargetPath>
</ItemGroup>
</Target>
</Project>

46
src/Grpc/Grpc.sln Normal file
View File

@ -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}") = "InteropTests", "test\InteropTests\InteropTests.csproj", "{90BF37E6-B3F1-4EFC-A233-8288D8B32DD2}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropWebsite", "test\testassets\InteropWebsite\InteropWebsite.csproj", "{3AB7E8E4-BA36-44CE-844E-39DB66E46D45}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "InteropClient", "test\testassets\InteropClient\InteropClient.csproj", "{66E6C55E-E4E3-4F4B-834A-BB34BFE00D2F}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{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
{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}
EndGlobalSection
EndGlobal

View File

@ -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.

3
src/Grpc/build.cmd Normal file
View File

@ -0,0 +1,3 @@
@ECHO OFF
SET RepoRoot=%~dp0..\..
%RepoRoot%\build.cmd -projects %~dp0**\*.*proj %*

7
src/Grpc/build.sh Normal file
View File

@ -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" "$@"

3
src/Grpc/startvs.cmd Normal file
View File

@ -0,0 +1,3 @@
@ECHO OFF
%~dp0..\..\startvs.cmd %~dp0Grpc.sln

View File

@ -0,0 +1,62 @@
// 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.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Xunit.Abstractions;
namespace InteropTests.Helpers
{
public class ClientProcess : IDisposable
{
private readonly Process _process;
private readonly ProcessEx _processEx;
private readonly TaskCompletionSource<object> _startTcs;
public ClientProcess(ITestOutputHelper output, string path, string serverPort, string testCase)
{
_process = new Process();
_process.StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = "dotnet",
Arguments = @$"{path} --use_tls false --server_port {serverPort} --client_type httpclient --test_case {testCase}"
};
_process.EnableRaisingEvents = true;
_process.OutputDataReceived += Process_OutputDataReceived;
_process.Start();
_processEx = new ProcessEx(output, _process);
_startTcs = new TaskCompletionSource<object>(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();
}
}
}

View File

@ -0,0 +1,44 @@
// 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.Helpers
{
public class InteropTestsFixture : IDisposable
{
private WebsiteProcess _process;
public string Path { get; set; }
public string ServerPort { get; private set; }
public async Task EnsureStarted(ITestOutputHelper output)
{
if (_process != null)
{
return;
}
if (string.IsNullOrEmpty(Path))
{
throw new InvalidOperationException("Path has not been set.");
}
_process = new WebsiteProcess(Path, output);
await _process.WaitForReady();
ServerPort = _process.ServerPort;
}
public void Dispose()
{
_process.Dispose();
}
}
}

View File

@ -0,0 +1,93 @@
// 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.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;
namespace InteropTests.Helpers
{
public class WebsiteProcess : IDisposable
{
private readonly Process _process;
private readonly ProcessEx _processEx;
private readonly TaskCompletionSource<object> _startTcs;
private readonly StringBuilder _consoleOut = new StringBuilder();
private static readonly Regex NowListeningRegex = new Regex(@"^\s*Now listening on: .*:(?<port>\d*)$");
public string ServerPort { get; private set; }
public WebsiteProcess(string path, ITestOutputHelper output)
{
_process = new Process();
_process.StartInfo = new ProcessStartInfo
{
RedirectStandardOutput = true,
RedirectStandardError = true,
FileName = "dotnet",
Arguments = path
};
_process.EnableRaisingEvents = true;
_process.OutputDataReceived += Process_OutputDataReceived;
_process.Start();
_processEx = new ProcessEx(output, _process);
_startTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
}
public Task WaitForReady()
{
if (_processEx.HasExited)
{
return Task.FromException(new InvalidOperationException("Server is not running."));
}
return _startTcs.Task;
}
private void Process_OutputDataReceived(object sender, DataReceivedEventArgs e)
{
var data = e.Data;
if (data != null)
{
_consoleOut.AppendLine(data);
var m = NowListeningRegex.Match(data);
if (m.Success)
{
ServerPort = m.Groups["port"].Value;
}
if (data.Contains("Application started. Press Ctrl+C to shut down."))
{
_startTcs.TrySetResult(null);
}
}
}
public void Dispose()
{
var attributes = Assembly.GetExecutingAssembly()
.GetCustomAttributes<AssemblyMetadataAttribute>();
var serverLogPath = attributes.SingleOrDefault(a => a.Key == "ServerLogPath")?.Value;
if (!string.IsNullOrEmpty(serverLogPath))
{
File.WriteAllText(serverLogPath, _consoleOut.ToString());
}
else
{
var logDir = Path.Combine(Directory.GetCurrentDirectory(), "artifacts", "logs");
Directory.CreateDirectory(logDir);
File.WriteAllText(Path.Combine(logDir, "InteropServer.log"), _consoleOut.ToString());
}
_processEx.Dispose();
}
}
}

View File

@ -0,0 +1,80 @@
// 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.IO;
using System.Linq;
using System.Reflection;
using System.Threading.Tasks;
using InteropTests.Helpers;
using Microsoft.AspNetCore.Testing;
using Xunit;
using Xunit.Abstractions;
namespace InteropTests
{
public class InteropTests : IClassFixture<InteropTestsFixture>
{
private static readonly TimeSpan DefaultTimeout = TimeSpan.FromSeconds(30);
private readonly string _clientPath = Path.Combine(Directory.GetCurrentDirectory(), "InteropClient", "InteropClient.dll");
private readonly InteropTestsFixture _fixture;
private readonly ITestOutputHelper _output;
public InteropTests(InteropTestsFixture fixture, ITestOutputHelper output)
{
var attributes = Assembly.GetExecutingAssembly()
.GetCustomAttributes<AssemblyMetadataAttribute>();
fixture.Path = Path.Combine(Directory.GetCurrentDirectory(), "InteropWebsite", "InteropWebsite.dll");
_fixture = fixture;
_output = output;
}
[Theory]
[MemberData(nameof(TestCaseData))]
public async Task InteropTestCase(string name)
{
await _fixture.EnsureStarted(_output).TimeoutAfter(DefaultTimeout);
using (var clientProcess = new ClientProcess(_output, _clientPath, _fixture.ServerPort, 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[]
{
"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<object[]> TestCaseData => AllTests.Select(t => new object[] { t });
#endregion
}
}

View File

@ -0,0 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<ContainsFunctionalTestAssets>true</ContainsFunctionalTestAssets>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
</PropertyGroup>
<ItemGroup>
<Compile Include="..\..\..\Shared\Process\ProcessEx.cs" Link="Helpers\ProcessEx.cs" />
<Compile Include="..\..\..\Shared\Process\ProcessExtensions.cs" Link="Helpers\ProcessExtensions.cs" />
<AssemblyAttribute Include="System.Reflection.AssemblyMetadataAttribute" Condition="'$(IsHelixJob)' != 'true'">
<_Parameter1>ServerLogPath</_Parameter1>
<_Parameter2>$(ArtifactsLogDir)InteropServer.log</_Parameter2>
</AssemblyAttribute>
<FunctionalTestAssetProjectReference Include="..\testassets\InteropClient\InteropClient.csproj" />
<FunctionalTestAssetProjectReference Include="..\testassets\InteropWebsite\InteropWebsite.csproj" />
</ItemGroup>
</Project>

View File

@ -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<TException> ThrowsAsync<TException>(Func<Task> 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<TException>(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]!);
}
}
}
}

View File

@ -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
{
/// <summary>
/// Extension methods that simplify work with gRPC streaming calls.
/// </summary>
public static class AsyncStreamExtensions
{
/// <summary>
/// Reads the entire stream and executes an async action for each element.
/// </summary>
public static async Task ForEachAsync<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction)
where T : class
{
while (await streamReader.MoveNext().ConfigureAwait(false))
{
await asyncAction(streamReader.Current).ConfigureAwait(false);
}
}
/// <summary>
/// Reads the entire stream and creates a list containing all the elements read.
/// </summary>
public static async Task<List<T>> ToListAsync<T>(this IAsyncStreamReader<T> streamReader)
where T : class
{
var result = new List<T>();
while (await streamReader.MoveNext().ConfigureAwait(false))
{
result.Add(streamReader.Current);
}
return result;
}
/// <summary>
/// Writes all elements from given enumerable to the stream.
/// Completes the stream afterwards unless close = false.
/// </summary>
public static async Task WriteAllAsync<T>(this IClientStreamWriter<T> streamWriter, IEnumerable<T> 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);
}
}
/// <summary>
/// Writes all elements from given enumerable to the stream.
/// </summary>
public static async Task WriteAllAsync<T>(this IServerStreamWriter<T> streamWriter, IEnumerable<T> elements)
where T : class
{
foreach (var element in elements)
{
await streamWriter.WriteAsync(element).ConfigureAwait(false);
}
}
}
}

View File

@ -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;
}
}
}

View File

@ -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<ILoggerFactory>();
}
public void Dispose()
{
serviceProvider.Dispose();
}
public static void Run(string[] args)
{
var parserResult = Parser.Default.ParseArguments<ClientOptions>(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().GetAwaiter().GetResult();
}
});
}
private async Task Run()
{
var channel = await HttpClientCreateChannel();
await RunTestCaseAsync(channel, options);
await channel.ShutdownAsync();
}
private async Task<IChannelWrapper> 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<ChannelCredentials> 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<TClient>(IChannelWrapper channel) where TClient : ClientBase
{
return (TClient)Activator.CreateInstance(typeof(TClient), channel.Channel)!;
}
private async Task RunTestCaseAsync(IChannelWrapper channel, ClientOptions options)
{
var client = CreateClient<TestService.TestServiceClient>(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<UnimplementedService.UnimplementedServiceClient>(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<int> { 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<int> { 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<RpcException>(() => 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<RpcException>(() => 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<RpcException>(() => 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<RpcException>(() => 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<RpcException>(() => 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<int> { 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<string>();
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));
}
}
}

View File

@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="..\Proto\grpc\testing\test.proto" GrpcServices="Client" Link="Protos\test.proto" />
<Protobuf Include="..\Proto\grpc\testing\empty.proto" GrpcServices="None" Link="Protos\empty.proto" />
<Protobuf Include="..\Proto\grpc\testing\messages.proto" GrpcServices="None" Link="Protos\messages.proto" />
<Reference Include="CommandLineParser" />
<Reference Include="Google.Protobuf" />
<Reference Include="Grpc.Auth" />
<Reference Include="Grpc.Net.Client" />
<Reference Include="Grpc.Tools" PrivateAssets="All" />
<Reference Include="Microsoft.Extensions.DependencyInjection" />
<Reference Include="Microsoft.Extensions.Logging.Console" />
</ItemGroup>
</Project>

View File

@ -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);
}
}
}

View File

@ -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

View File

@ -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
{
/// <summary>
/// Reads the entire stream and executes an async action for each element.
/// </summary>
public static async Task ForEachAsync<T>(this IAsyncStreamReader<T> streamReader, Func<T, Task> asyncAction)
where T : class
{
while (await streamReader.MoveNext().ConfigureAwait(false))
{
await asyncAction(streamReader.Current).ConfigureAwait(false);
}
}
}
}

View File

@ -0,0 +1,24 @@
<Project>
<!-- Skip the parent folder to prevent getting test package references. -->
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory)..\..\, Directory.Build.targets))\Directory.Build.targets" />
<PropertyGroup>
<RestoreAdditionalProjectSources>$(RestoreAdditionalProjectSources);$(ArtifactsShippingPackagesDir)</RestoreAdditionalProjectSources>
<MicrosoftAspNetCoreAppRefPackageVersion Condition="'$(IsTargetingPackBuilding)' != 'false'">$(TargetingPackVersion)</MicrosoftAspNetCoreAppRefPackageVersion>
<MicrosoftAspNetCoreAppRefPackageVersion Condition="'$(IsTargetingPackBuilding)' == 'false'">$(AspNetCoreBaselineVersion)</MicrosoftAspNetCoreAppRefPackageVersion>
</PropertyGroup>
<!-- Use the shared framework that was produced -->
<ItemGroup>
<KnownFrameworkReference
Include="Microsoft.AspNetCore.App"
TargetFramework="$(DefaultNetCoreTargetFramework)"
RuntimeFrameworkName="Microsoft.AspNetCore.App"
TargetingPackName="Microsoft.AspNetCore.App.Ref"
RuntimePackNamePatterns="Microsoft.AspNetCore.App.Runtime.**RID**"
DefaultRuntimeFrameworkVersion="$(SharedFxVersion)"
LatestRuntimeFrameworkVersion="$(SharedFxVersion)"
TargetingPackVersion="$(MicrosoftAspNetCoreAppRefPackageVersion)"
RuntimePackRuntimeIdentifiers="$(SupportedRuntimeIdentifiers)" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,19 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>$(DefaultNetCoreTargetFramework)</TargetFramework>
<AspNetCoreHostingModel>InProcess</AspNetCoreHostingModel>
<GenerateUserSecretsAttribute>false</GenerateUserSecretsAttribute>
<Nullable>enable</Nullable>
<LangVersion>8.0</LangVersion>
</PropertyGroup>
<ItemGroup>
<Protobuf Include="..\Proto\grpc\testing\test.proto" GrpcServices="Server" Link="Protos\test.proto" />
<Protobuf Include="..\Proto\grpc\testing\empty.proto" GrpcServices="None" Link="Protos\empty.proto" />
<Protobuf Include="..\Proto\grpc\testing\messages.proto" GrpcServices="None" Link="Protos\messages.proto" />
<Reference Include="Grpc.AspNetCore" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,63 @@
#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.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 useTls = context.Configuration.GetValue("use_tls", false);
options.Limits.MinRequestBodyDataRate = null;
options.ListenAnyIP(0, 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<Startup>();
});
}
}

View File

@ -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<TestServiceImpl>();
});
}
}
}

View File

@ -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<Empty> EmptyCall(Empty request, ServerCallContext context)
{
return Task.FromResult(new Empty());
}
public override async Task<SimpleResponse> 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<StreamingOutputCallResponse> 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<StreamingInputCallResponse> StreamingInputCall(IAsyncStreamReader<StreamingInputCallRequest> 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<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> 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<StreamingOutputCallRequest> requestStream, IServerStreamWriter<StreamingOutputCallResponse> 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));
}
}
}
}
}
}

View File

@ -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 {}

View File

@ -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;
}

View File

@ -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);
}

View File

@ -0,0 +1,3 @@
InteropTestsClient and InteropTestsWebsite are copied from [grpc-dotnet](https://github.com/grpc/grpc-dotnet/tree/master/testassets).
For more information about the interop tests, see the [interop tests specification](https://github.com/grpc/grpc/blob/master/doc/interop-test-descriptions.md).

View File

@ -11,6 +11,7 @@ using System.Threading.Tasks;
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;

View File

@ -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

View File

@ -10,6 +10,7 @@ using System.Reflection;
using System.Runtime.InteropServices;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.CommandLineUtils;
using Xunit;
using Xunit.Abstractions;

View File

@ -8,6 +8,7 @@ using System.Linq;
using System.Reflection;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Internal;
using Microsoft.Extensions.CommandLineUtils;
using Xunit;
using Xunit.Abstractions;

View File

@ -10,6 +10,7 @@ using System.Net.Http;
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;

View File

@ -15,7 +15,7 @@ using System.Threading.Tasks;
using Microsoft.Extensions.Internal;
using Xunit.Abstractions;
namespace Templates.Test.Helpers
namespace Microsoft.AspNetCore.Internal
{
internal class ProcessEx : IDisposable
{
@ -100,7 +100,7 @@ namespace Templates.Test.Helpers
}
startInfo.EnvironmentVariables["NUGET_PACKAGES"] = NUGET_PACKAGES;
if (!string.IsNullOrEmpty(Environment.GetEnvironmentVariable("helix")))
{
startInfo.EnvironmentVariables["NUGET_FALLBACK_PACKAGES"] = Environment.GetEnvironmentVariable("NUGET_FALLBACK_PACKAGES");
@ -195,7 +195,7 @@ namespace Templates.Test.Helpers
}
}
private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE")))
private static string GetNugetPackagesRestorePath() => (string.IsNullOrEmpty(Environment.GetEnvironmentVariable("NUGET_RESTORE")))
? typeof(ProcessEx).Assembly
.GetCustomAttributes<AssemblyMetadataAttribute>()
.First(attribute => attribute.Key == "TestPackageRestorePath")