chore: Reorganize examples and interop crates (#180)
* chore: Reorganize examples and interop crates * fix interop tests
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
[package]
|
||||
name = "tonic-interop"
|
||||
version = "0.1.0"
|
||||
authors = ["Lucio Franco <[email protected]>"]
|
||||
edition = "2018"
|
||||
publish = false
|
||||
license = "MIT"
|
||||
|
||||
[[bin]]
|
||||
name = "client"
|
||||
path = "src/bin/client.rs"
|
||||
|
||||
[[bin]]
|
||||
name = "server"
|
||||
path = "src/bin/server.rs"
|
||||
|
||||
[dependencies]
|
||||
tokio = { version = "0.2", features = ["rt-threaded", "time", "macros", "stream", "fs"] }
|
||||
tonic = { path = "../tonic", features = ["tls"] }
|
||||
prost = "0.5"
|
||||
prost-derive = "0.5"
|
||||
bytes = "0.4"
|
||||
http = "0.2"
|
||||
futures-core = "0.3"
|
||||
futures-util = "0.3"
|
||||
async-stream = "0.2"
|
||||
# tower = "=0.3.0-alpha.2"
|
||||
tower = { git = "https://github.com/tower-rs/tower" }
|
||||
http-body = "0.3"
|
||||
|
||||
console = "0.9"
|
||||
structopt = "0.2"
|
||||
|
||||
tracing = "0.1"
|
||||
tracing-subscriber = "0.2.0-alpha"
|
||||
tracing-log = "0.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
tonic-build = { path = "../tonic-build" }
|
||||
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
Executable
BIN
Binary file not shown.
@@ -0,0 +1,8 @@
|
||||
fn main() {
|
||||
let proto = "proto/grpc/testing/test.proto";
|
||||
|
||||
tonic_build::compile_protos(proto).unwrap();
|
||||
|
||||
// prevent needing to rebuild if files (or deps) haven't changed
|
||||
println!("cargo:rerun-if-changed={}", proto);
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
# Tonic Testing Certificates
|
||||
|
||||
This directory contains certificates used for testing interop between Tonic's
|
||||
implementation of gRPC and the Go implementation. Certificates are generated
|
||||
using [`terraform`][tf].
|
||||
|
||||
To regenerate certificates for some reason, do the following:
|
||||
|
||||
1. Install Terraform 0.12 (or higher)
|
||||
1. From the `cert-generator` directory, run:
|
||||
1. `terraform init`
|
||||
1. `terraform apply`
|
||||
|
||||
This will generate certificates and write them to the filesystem. The effective
|
||||
version should be committed to git.
|
||||
|
||||
[tf]: https://terraform.io
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDRzCCAi+gAwIBAgIRAO7dzPqhReVW2U6D1V1DTYAwDQYJKoZIhvcNAQELBQAw
|
||||
PTEOMAwGA1UEChMFVG9raW8xEDAOBgNVBAsTB1Rlc3RpbmcxGTAXBgNVBAMTEFRv
|
||||
bmljIFRlc3RpbmcgQ0EwHhcNMTkxMTA5MTY0NzU0WhcNMjkxMTA2MTY0NzU0WjA9
|
||||
MQ4wDAYDVQQKEwVUb2tpbzEQMA4GA1UECxMHVGVzdGluZzEZMBcGA1UEAxMQVG9u
|
||||
aWMgVGVzdGluZyBDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAM+A
|
||||
e+Y3wDdOm7yUi6xHINw1QE0k1D51U+DAMOLcUq17FYh3fp+OOaqq4znnEx8WkKVl
|
||||
FuoW4xzIrv2ywn0hFADCxaVpjMuCxj313D7LMZExa98TuFF3Jg2GYScBRQKjfyRv
|
||||
CV+cSHAvzEstd5ckdiz985Zqnepiy7R9k2CstO45ULG4UoVha+VgmYJ5qXqBXbso
|
||||
LbDXzrjjQFSmbw1yh9lQvzsk0UyU5Hvi0Otka4LZJsNaNFWkltl8v37QWG6sH8+f
|
||||
Ur7fELIDmbScGSiqvEm0EZLZHqLU669NvUoFtTKNfKghT83DW9kErzdg5EcVFQom
|
||||
Ov8cIW9MkBwDJXe6D88CAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgIEMA8GA1UdEwEB
|
||||
/wQFMAMBAf8wHQYDVR0OBBYEFE5N36NK1qLWGDxSJP3IF9H2oLO/MA0GCSqGSIb3
|
||||
DQEBCwUAA4IBAQAm27Wdq6+0IxvattJW0j7lnh9AC7aMQEbUQPYM8iO6WVhXshaN
|
||||
d7YbLDGwRtATzLeA/0laovEL5pc/1NnMsqG4DspD3glXcASUiu5TrzPvBrAWByQn
|
||||
8C9tGUbyP1yJMo42Mzs0hIDsaUsucynZcdxAErjJjKB9Ps3KeaA6LUr/igN0649S
|
||||
qQ6bHZdNEIhjyCY7TDJArjPTgdSYjx13XkVbZ1cN/ssZI7Ag8WByhVEHeoE13fqw
|
||||
ue5oTNat0qNFP0Yo8XB0whPhN2wbCbHoc5khXJiBrMEjAAgJUiWRy3ITG88SIak+
|
||||
1dtqgxS2T0nmdRlXBYNNhYKdgWYJq1PwDp9a
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,3 @@
|
||||
.terraform/
|
||||
*.tfstate
|
||||
*.tfstate.backup
|
||||
@@ -0,0 +1,27 @@
|
||||
resource "tls_private_key" "root" {
|
||||
algorithm = "RSA"
|
||||
rsa_bits = "2048"
|
||||
}
|
||||
|
||||
resource "tls_self_signed_cert" "root" {
|
||||
key_algorithm = tls_private_key.root.algorithm
|
||||
private_key_pem = tls_private_key.root.private_key_pem
|
||||
|
||||
validity_period_hours = 87600
|
||||
early_renewal_hours = 8760
|
||||
|
||||
is_ca_certificate = true
|
||||
|
||||
allowed_uses = ["cert_signing"]
|
||||
|
||||
subject {
|
||||
common_name = "Tonic Testing CA"
|
||||
organization = "Tokio"
|
||||
organizational_unit = "Testing"
|
||||
}
|
||||
}
|
||||
|
||||
resource "local_file" "ca_cert" {
|
||||
filename = "../ca.pem"
|
||||
content = tls_self_signed_cert.root.cert_pem
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
resource "tls_private_key" "server" {
|
||||
algorithm = "RSA"
|
||||
rsa_bits = "2048"
|
||||
}
|
||||
|
||||
resource "tls_cert_request" "server" {
|
||||
key_algorithm = tls_private_key.server.algorithm
|
||||
private_key_pem = tls_private_key.server.private_key_pem
|
||||
|
||||
subject {
|
||||
common_name = "Tonic Test Server Cert"
|
||||
}
|
||||
|
||||
dns_names = [
|
||||
"*.test.google.fr",
|
||||
]
|
||||
}
|
||||
|
||||
resource "tls_locally_signed_cert" "server" {
|
||||
cert_request_pem = tls_cert_request.server.cert_request_pem
|
||||
|
||||
ca_key_algorithm = tls_private_key.root.algorithm
|
||||
ca_private_key_pem = tls_private_key.root.private_key_pem
|
||||
ca_cert_pem = tls_self_signed_cert.root.cert_pem
|
||||
|
||||
validity_period_hours = 43800
|
||||
early_renewal_hours = 8760
|
||||
|
||||
allowed_uses = ["server_auth"]
|
||||
}
|
||||
|
||||
resource "local_file" "server_cert" {
|
||||
filename = "../server1.pem"
|
||||
content = tls_locally_signed_cert.server.cert_pem
|
||||
}
|
||||
|
||||
resource "local_file" "server_key" {
|
||||
filename = "../server1.key"
|
||||
content = tls_private_key.server.private_key_pem
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
-----BEGIN RSA PRIVATE KEY-----
|
||||
MIIEpAIBAAKCAQEAvSo8ttodDg4wwjwSPURcTgVPh7Iv9E+PsCTHW7b1Dov/tjxs
|
||||
nhgLZkySicSLIhHv+/17DYHaMd7alx5AYb+Bxk/KvSuR/sqg0QJcuQ4prpzccQHY
|
||||
mqJTeMWFCOvAUSSOZXwOxKHFNoLCVQsZMMs1JmUwAoun9ocyuoXuNFa/2GmNO+cN
|
||||
tBSa1ZnEyhuW9R8lFdp41e9OG1c6dgdh3Gq6NEqDaRN/FbaBAQJO6u8jDClcqeB5
|
||||
uPfXKEENk8ed71zRRJ7u4Pjn+qCgy9daz6hRuP7MGKnrXt1j5f73JnZF5bFuM+Bg
|
||||
6MqG6XVvrIVDdvod2qWpMTjCpINp45i0lkZpAQIDAQABAoIBAQCu50LD/uAmgtBq
|
||||
h4iFxZNjQF3MpeDZEEdXImqCTqQ/EwsYwL3dX3YK3HoRj/zlP5iZckI4tvu8aMXM
|
||||
PFhjCONBLb3TM1oGL+yJ1JlPMd0wajEY/A/+ymBLprXfDbwASsCu7QnqnXjvce+l
|
||||
GmHsT7eRDLZbZC2lMFSjSfp5wkwYF8lBfg2tDswgBX2mT4lZ/nUIjpr1x1y34BQL
|
||||
yPYHSsSnoTSC6dqWkpuiQyyTraeknCsE4lw03C2XWcpCFaNt6ZrzAmSeX7/MK+Xp
|
||||
blQ4bQmeydCwfsXFBdE/lOWUIjc8mkBN0c9wbiBJwyAH09QzUVcXlgjH7IBwKSMc
|
||||
mFpLvt3BAoGBAOGNHc1URPiSJaIc86nkWvmNiISxqHUVOrZPwOLJMciJz75XSmVs
|
||||
QlydXIepunU5WZJJiIV2fL6FyZ7RnCGLCvFEymewobexAjL3ffN4oN5s2g2e8fs+
|
||||
vhu3xLxo3fmhcRLbxub0qOJUu//2lcBYRXHVu+lb4qvisnn4BDMxbAFNAoGBANaz
|
||||
o5Zo6uL5WHcNjwKa4YtWA4/pzAzsXwNqPPUyWL9uvAP3YS7ikQvzLJXDUTCtopZ5
|
||||
xTvuShRPfvrIUkvK9XOok3PNeyPeBoRQ8W9sP6n/5gSULy7sHALJxZscMf7GD81+
|
||||
yfH+R+67vwW1etwP3LHuC1NirCcpvLZfWyfUcqyFAoGBAKeqzXKrqDHYAp3GQ+QR
|
||||
WweUDN4HayDOTTzlgI+V3KokuAfYv/cxSQur9vLqWy91GH7EpvX/pK/EqKKlUxkk
|
||||
UVgVORlnlnAE54uXq0toar2t0VK6y0tn0s6sB1W/5vMA7huEwRFC4qCNOMwIND4t
|
||||
4EHFDtFketYnyWEd25Fqtc0pAoGATpvJClnxnhbDMBuzv7VrXPOqLDfisNyeUQbF
|
||||
uNStL7HgfudFGsBzcNeg/Fhd0p/QRp3g+/dcAiG1ESblEsEFq0oOarjSHCi/ZBSq
|
||||
wSv2B00dL5H90IU8ID0173ucRnbH9Go2kDaUqbDt2K5AhG/+UtsgJHCdLV2XrYIu
|
||||
QuAC+G0CgYBW2O4FjDLG16kivtEziCW+Z/iLeUM3r8lDzf8iz1Dg+72AnlmAFSzz
|
||||
QaY2GxdatZHBCMeZ0eaMxxFyuT2IPKOiyIHQ6diJBX+CFNHxgPfCAQ8DTVi6aJtS
|
||||
NiY/yUYTsQbh6Mey8QAC5wdoyuVUKQO2SwyNiHFWP+i9aFXr/rv98w==
|
||||
-----END RSA PRIVATE KEY-----
|
||||
@@ -0,0 +1,20 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIDSzCCAjOgAwIBAgIQQY5jNBnC4CbgToZB9CzLYzANBgkqhkiG9w0BAQsFADA9
|
||||
MQ4wDAYDVQQKEwVUb2tpbzEQMA4GA1UECxMHVGVzdGluZzEZMBcGA1UEAxMQVG9u
|
||||
aWMgVGVzdGluZyBDQTAeFw0xOTExMDkxNjQ3NTRaFw0yNDExMDcxNjQ3NTRaMCEx
|
||||
HzAdBgNVBAMTFlRvbmljIFRlc3QgU2VydmVyIENlcnQwggEiMA0GCSqGSIb3DQEB
|
||||
AQUAA4IBDwAwggEKAoIBAQC9Kjy22h0ODjDCPBI9RFxOBU+Hsi/0T4+wJMdbtvUO
|
||||
i/+2PGyeGAtmTJKJxIsiEe/7/XsNgdox3tqXHkBhv4HGT8q9K5H+yqDRAly5Dimu
|
||||
nNxxAdiaolN4xYUI68BRJI5lfA7EocU2gsJVCxkwyzUmZTACi6f2hzK6he40Vr/Y
|
||||
aY075w20FJrVmcTKG5b1HyUV2njV704bVzp2B2Hcaro0SoNpE38VtoEBAk7q7yMM
|
||||
KVyp4Hm499coQQ2Tx53vXNFEnu7g+Of6oKDL11rPqFG4/swYqete3WPl/vcmdkXl
|
||||
sW4z4GDoyobpdW+shUN2+h3apakxOMKkg2njmLSWRmkBAgMBAAGjYzBhMBMGA1Ud
|
||||
JQQMMAoGCCsGAQUFBwMBMAwGA1UdEwEB/wQCMAAwHwYDVR0jBBgwFoAUTk3fo0rW
|
||||
otYYPFIk/cgX0fags78wGwYDVR0RBBQwEoIQKi50ZXN0Lmdvb2dsZS5mcjANBgkq
|
||||
hkiG9w0BAQsFAAOCAQEATypihaP3RfHar9DZGeKwPz25BQBhGJE2lEwlehUuNBkG
|
||||
M1+yQw0p3Yj2n5tR87Qp+G5rDN51e2OCWbEMTo0iAiGdic4N4FXnmA7R9QDcBcKw
|
||||
YmKIXd48F+Ceh5XYGFuR14dTshu2Zajwcw4OW3dhvEbv9h5pMCJXplhAPHvhTKTM
|
||||
TTl0fjDkBcOdKWswdQCtt2xOqcQhpYdVYClYym22WYDcMDr7CqiC/y3jLl6eVpsA
|
||||
hPDAVZqpZSpnV6isihoUyDeSVT830/E/n8e756l7gBNxpsYF+PqDBVPiAU7Gjp99
|
||||
EegYn7LiB+s4tgHeSMdNclffWQJ3PameoaLHdikGLA==
|
||||
-----END CERTIFICATE-----
|
||||
@@ -0,0 +1,27 @@
|
||||
// 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 {}
|
||||
@@ -0,0 +1,168 @@
|
||||
// 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;
|
||||
}
|
||||
|
||||
// DEPRECATED, don't use. To be removed shortly.
|
||||
// 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 {
|
||||
// DEPRECATED, don't use. To be removed shortly.
|
||||
// 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 {
|
||||
// DEPRECATED, don't use. To be removed shortly.
|
||||
// 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 {
|
||||
// DEPRECATED, don't use. To be removed shortly.
|
||||
// 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;
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
// 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);
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
use std::time::Duration;
|
||||
use structopt::{clap::arg_enum, StructOpt};
|
||||
use tonic::transport::Endpoint;
|
||||
use tonic::transport::{Certificate, ClientTlsConfig};
|
||||
use tonic_interop::client;
|
||||
|
||||
#[derive(StructOpt)]
|
||||
struct Opts {
|
||||
#[structopt(
|
||||
long = "test_case",
|
||||
use_delimiter = true,
|
||||
min_values = 1,
|
||||
raw(possible_values = r#"&Testcase::variants()"#)
|
||||
)]
|
||||
test_case: Vec<Testcase>,
|
||||
|
||||
#[structopt(long)]
|
||||
use_tls: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_interop::trace_init();
|
||||
|
||||
let matches = Opts::from_args();
|
||||
|
||||
let test_cases = matches.test_case;
|
||||
|
||||
#[allow(unused_mut)]
|
||||
let mut endpoint = Endpoint::from_static("http://localhost:10000")
|
||||
.timeout(Duration::from_secs(5))
|
||||
.concurrency_limit(30);
|
||||
|
||||
if matches.use_tls {
|
||||
let pem = tokio::fs::read("interop/data/ca.pem").await?;
|
||||
let ca = Certificate::from_pem(pem);
|
||||
endpoint = endpoint.tls_config(
|
||||
ClientTlsConfig::with_rustls()
|
||||
.ca_certificate(ca)
|
||||
.domain_name("foo.test.google.fr"),
|
||||
);
|
||||
}
|
||||
|
||||
let channel = endpoint.connect().await?;
|
||||
|
||||
let mut client = client::TestClient::new(channel.clone());
|
||||
let mut unimplemented_client = client::UnimplementedClient::new(channel);
|
||||
|
||||
let mut failures = Vec::new();
|
||||
|
||||
for test_case in test_cases {
|
||||
println!("{:?}:", test_case);
|
||||
let mut test_results = Vec::new();
|
||||
|
||||
match test_case {
|
||||
Testcase::empty_unary => client::empty_unary(&mut client, &mut test_results).await,
|
||||
Testcase::large_unary => client::large_unary(&mut client, &mut test_results).await,
|
||||
Testcase::client_streaming => {
|
||||
client::client_streaming(&mut client, &mut test_results).await
|
||||
}
|
||||
Testcase::server_streaming => {
|
||||
client::server_streaming(&mut client, &mut test_results).await
|
||||
}
|
||||
Testcase::ping_pong => client::ping_pong(&mut client, &mut test_results).await,
|
||||
Testcase::empty_stream => client::empty_stream(&mut client, &mut test_results).await,
|
||||
Testcase::status_code_and_message => {
|
||||
client::status_code_and_message(&mut client, &mut test_results).await
|
||||
}
|
||||
Testcase::special_status_message => {
|
||||
client::special_status_message(&mut client, &mut test_results).await
|
||||
}
|
||||
Testcase::unimplemented_method => {
|
||||
client::unimplemented_method(&mut client, &mut test_results).await
|
||||
}
|
||||
Testcase::unimplemented_service => {
|
||||
client::unimplemented_service(&mut unimplemented_client, &mut test_results).await
|
||||
}
|
||||
Testcase::custom_metadata => {
|
||||
client::custom_metadata(&mut client, &mut test_results).await
|
||||
}
|
||||
_ => unimplemented!(),
|
||||
}
|
||||
|
||||
for result in test_results {
|
||||
println!(" {}", result);
|
||||
|
||||
if result.is_failed() {
|
||||
failures.push(result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if !failures.is_empty() {
|
||||
println!("{} tests failed", failures.len());
|
||||
std::process::exit(1);
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
arg_enum! {
|
||||
#[derive(Debug, Copy, Clone)]
|
||||
#[allow(non_camel_case_types)]
|
||||
enum Testcase {
|
||||
empty_unary,
|
||||
cacheable_unary,
|
||||
large_unary,
|
||||
client_compressed_unary,
|
||||
server_compressed_unary,
|
||||
client_streaming,
|
||||
client_compressed_streaming,
|
||||
server_streaming,
|
||||
server_compressed_streaming,
|
||||
ping_pong,
|
||||
empty_stream,
|
||||
compute_engine_creds,
|
||||
jwt_token_creds,
|
||||
oauth2_auth_token,
|
||||
per_rpc_creds,
|
||||
custom_metadata,
|
||||
status_code_and_message,
|
||||
special_status_message,
|
||||
unimplemented_method,
|
||||
unimplemented_service,
|
||||
cancel_after_begin,
|
||||
cancel_after_first_response,
|
||||
timeout_on_sleeping_server,
|
||||
concurrent_large_unary
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
use http::header::HeaderName;
|
||||
use structopt::StructOpt;
|
||||
use tonic::body::BoxBody;
|
||||
use tonic::client::GrpcService;
|
||||
use tonic::transport::Server;
|
||||
use tonic::transport::{Identity, ServerTlsConfig};
|
||||
use tonic_interop::{server, MergeTrailers};
|
||||
|
||||
#[derive(StructOpt)]
|
||||
struct Opts {
|
||||
#[structopt(long)]
|
||||
use_tls: bool,
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
tonic_interop::trace_init();
|
||||
|
||||
let matches = Opts::from_args();
|
||||
|
||||
let addr = "127.0.0.1:10000".parse().unwrap();
|
||||
|
||||
let mut builder = Server::builder().interceptor_fn(|svc, req| {
|
||||
let echo_header = req
|
||||
.headers()
|
||||
.get("x-grpc-test-echo-initial")
|
||||
.map(Clone::clone);
|
||||
|
||||
let echo_trailer = req
|
||||
.headers()
|
||||
.get("x-grpc-test-echo-trailing-bin")
|
||||
.map(Clone::clone)
|
||||
.map(|v| (HeaderName::from_static("x-grpc-test-echo-trailing-bin"), v));
|
||||
|
||||
let call = svc.call(req);
|
||||
|
||||
async move {
|
||||
let mut res = call.await?;
|
||||
|
||||
if let Some(echo_header) = echo_header {
|
||||
res.headers_mut()
|
||||
.insert("x-grpc-test-echo-initial", echo_header);
|
||||
}
|
||||
|
||||
Ok(res
|
||||
.map(|b| MergeTrailers::new(b, echo_trailer))
|
||||
.map(BoxBody::new))
|
||||
}
|
||||
});
|
||||
|
||||
if matches.use_tls {
|
||||
let cert = tokio::fs::read("interop/data/server1.pem").await?;
|
||||
let key = tokio::fs::read("interop/data/server1.key").await?;
|
||||
let identity = Identity::from_pem(cert, key);
|
||||
|
||||
builder = builder.tls_config(ServerTlsConfig::with_rustls().identity(identity));
|
||||
}
|
||||
|
||||
let test_service = server::TestServiceServer::new(server::TestService::default());
|
||||
let unimplemented_service =
|
||||
server::UnimplementedServiceServer::new(server::UnimplementedService::default());
|
||||
|
||||
builder
|
||||
.add_service(test_service)
|
||||
.add_service(unimplemented_service)
|
||||
.serve(addr)
|
||||
.await?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -0,0 +1,409 @@
|
||||
use crate::{
|
||||
pb::testservice_client::*, pb::unimplementedservice_client::*, pb::*, test_assert,
|
||||
TestAssertion,
|
||||
};
|
||||
use futures_util::{future, stream, StreamExt};
|
||||
use tokio::sync::mpsc;
|
||||
use tonic::transport::Channel;
|
||||
use tonic::{metadata::MetadataValue, Code, Request, Response, Status};
|
||||
|
||||
pub type TestClient = TestServiceClient<Channel>;
|
||||
pub type UnimplementedClient = UnimplementedServiceClient<Channel>;
|
||||
|
||||
const LARGE_REQ_SIZE: usize = 271828;
|
||||
const LARGE_RSP_SIZE: i32 = 314159;
|
||||
const REQUEST_LENGTHS: &'static [i32] = &[27182, 8, 1828, 45904];
|
||||
const RESPONSE_LENGTHS: &'static [i32] = &[31415, 9, 2653, 58979];
|
||||
const TEST_STATUS_MESSAGE: &'static str = "test status message";
|
||||
const SPECIAL_TEST_STATUS_MESSAGE: &'static str =
|
||||
"\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n";
|
||||
|
||||
pub async fn empty_unary(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let result = client.empty_call(Request::new(Empty {})).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must be successful",
|
||||
result.is_ok(),
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
if let Ok(response) = result {
|
||||
let body = response.into_inner();
|
||||
assertions.push(test_assert!(
|
||||
"body must not be null",
|
||||
body == Empty {},
|
||||
format!("body={:?}", body)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn large_unary(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
use std::mem;
|
||||
let payload = crate::client_payload(LARGE_REQ_SIZE);
|
||||
let req = SimpleRequest {
|
||||
response_type: PayloadType::Compressable as i32,
|
||||
response_size: LARGE_RSP_SIZE,
|
||||
payload: Some(payload),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = client.unary_call(Request::new(req)).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must be successful",
|
||||
result.is_ok(),
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
if let Ok(response) = result {
|
||||
let body = response.into_inner();
|
||||
let payload_len = body.payload.as_ref().map(|p| p.body.len()).unwrap_or(0);
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"body must be 314159 bytes",
|
||||
payload_len == LARGE_RSP_SIZE as usize,
|
||||
format!("mem::size_of_val(&body)={:?}", mem::size_of_val(&body))
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
// pub async fn cachable_unary(client: &mut Client, assertions: &mut Vec<TestAssertion>) {
|
||||
// let payload = Payload {
|
||||
// r#type: PayloadType::Compressable as i32,
|
||||
// body: format!("{:?}", std::time::Instant::now()).into_bytes(),
|
||||
// };
|
||||
// let req = SimpleRequest {
|
||||
// response_type: PayloadType::Compressable as i32,
|
||||
// payload: Some(payload),
|
||||
// ..Default::default()
|
||||
// };
|
||||
|
||||
// client.
|
||||
// }
|
||||
|
||||
pub async fn client_streaming(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let requests = REQUEST_LENGTHS.iter().map(|len| StreamingInputCallRequest {
|
||||
payload: Some(crate::client_payload(*len as usize)),
|
||||
..Default::default()
|
||||
});
|
||||
|
||||
let stream = stream::iter(requests);
|
||||
|
||||
let result = client.streaming_input_call(Request::new(stream)).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must be successful",
|
||||
result.is_ok(),
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
if let Ok(response) = result {
|
||||
let body = response.into_inner();
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"aggregated payload size must be 74922 bytes",
|
||||
body.aggregated_payload_size == 74922,
|
||||
format!("aggregated_payload_size={:?}", body.aggregated_payload_size)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn server_streaming(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let req = StreamingOutputCallRequest {
|
||||
response_parameters: RESPONSE_LENGTHS
|
||||
.iter()
|
||||
.map(|len| ResponseParameters::with_size(*len))
|
||||
.collect(),
|
||||
..Default::default()
|
||||
};
|
||||
let req = Request::new(req);
|
||||
|
||||
let result = client.streaming_output_call(req).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must be successful",
|
||||
result.is_ok(),
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
if let Ok(response) = result {
|
||||
let responses = response
|
||||
.into_inner()
|
||||
.filter_map(|m| future::ready(m.ok()))
|
||||
.collect::<Vec<_>>()
|
||||
.await;
|
||||
let actual_response_lengths = crate::response_lengths(&responses);
|
||||
let asserts = vec![
|
||||
test_assert!(
|
||||
"there should be four responses",
|
||||
responses.len() == 4,
|
||||
format!("responses.len()={:?}", responses.len())
|
||||
),
|
||||
test_assert!(
|
||||
"the response payload sizes should match input",
|
||||
RESPONSE_LENGTHS == actual_response_lengths.as_slice(),
|
||||
format!("{:?}={:?}", RESPONSE_LENGTHS, actual_response_lengths)
|
||||
),
|
||||
];
|
||||
|
||||
assertions.extend(asserts);
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn ping_pong(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
tx.send(make_ping_pong_request(0)).unwrap();
|
||||
|
||||
let result = client.full_duplex_call(Request::new(rx)).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must be successful",
|
||||
result.is_ok(),
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
if let Ok(mut response) = result.map(Response::into_inner) {
|
||||
let mut responses = Vec::new();
|
||||
|
||||
loop {
|
||||
match response.next().await {
|
||||
Some(result) => {
|
||||
responses.push(result.unwrap());
|
||||
if responses.len() == REQUEST_LENGTHS.len() {
|
||||
drop(tx);
|
||||
break;
|
||||
} else {
|
||||
tx.send(make_ping_pong_request(responses.len())).unwrap();
|
||||
}
|
||||
}
|
||||
None => {
|
||||
assertions.push(TestAssertion::Failed {
|
||||
description:
|
||||
"server should keep the stream open until the client closes it",
|
||||
expression: "Stream terminated unexpectedly early",
|
||||
why: None,
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let actual_response_lengths = crate::response_lengths(&responses);
|
||||
assertions.push(test_assert!(
|
||||
"there should be four responses",
|
||||
responses.len() == RESPONSE_LENGTHS.len(),
|
||||
format!("{:?}={:?}", responses.len(), RESPONSE_LENGTHS.len())
|
||||
));
|
||||
assertions.push(test_assert!(
|
||||
"the response payload sizes should match input",
|
||||
RESPONSE_LENGTHS == actual_response_lengths.as_slice(),
|
||||
format!("{:?}={:?}", RESPONSE_LENGTHS, actual_response_lengths)
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn empty_stream(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let stream = stream::iter(Vec::new());
|
||||
let result = client.full_duplex_call(Request::new(stream)).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must be successful",
|
||||
result.is_ok(),
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
if let Ok(response) = result.map(Response::into_inner) {
|
||||
let responses = response.collect::<Vec<_>>().await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"there should be no responses",
|
||||
responses.len() == 0,
|
||||
format!("responses.len()={:?}", responses.len())
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn status_code_and_message(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
fn validate_response<T>(result: Result<T, Status>, assertions: &mut Vec<TestAssertion>)
|
||||
where
|
||||
T: std::fmt::Debug,
|
||||
{
|
||||
assertions.push(test_assert!(
|
||||
"call must fail with unknown status code",
|
||||
match &result {
|
||||
Err(status) => status.code() == Code::Unknown,
|
||||
_ => false,
|
||||
},
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must respsond with expected status message",
|
||||
match &result {
|
||||
Err(status) => status.message() == TEST_STATUS_MESSAGE,
|
||||
_ => false,
|
||||
},
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
}
|
||||
|
||||
let simple_req = SimpleRequest {
|
||||
response_status: Some(EchoStatus {
|
||||
code: 2,
|
||||
message: TEST_STATUS_MESSAGE.to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let duplex_req = StreamingOutputCallRequest {
|
||||
response_status: Some(EchoStatus {
|
||||
code: 2,
|
||||
message: TEST_STATUS_MESSAGE.to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = client.unary_call(Request::new(simple_req)).await;
|
||||
validate_response(result, assertions);
|
||||
|
||||
let stream = stream::iter(vec![duplex_req]);
|
||||
let result = match client.full_duplex_call(Request::new(stream)).await {
|
||||
Ok(response) => {
|
||||
let stream = response.into_inner();
|
||||
let responses = stream.collect::<Vec<_>>().await;
|
||||
Ok(responses)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
};
|
||||
|
||||
validate_response(result, assertions);
|
||||
}
|
||||
|
||||
pub async fn special_status_message(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let req = SimpleRequest {
|
||||
response_status: Some(EchoStatus {
|
||||
code: 2,
|
||||
message: SPECIAL_TEST_STATUS_MESSAGE.to_string(),
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
let result = client.unary_call(Request::new(req)).await;
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must fail with unknown status code",
|
||||
match &result {
|
||||
Err(status) => status.code() == Code::Unknown,
|
||||
_ => false,
|
||||
},
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"call must respsond with expected status message",
|
||||
match &result {
|
||||
Err(status) => status.message() == SPECIAL_TEST_STATUS_MESSAGE,
|
||||
_ => false,
|
||||
},
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn unimplemented_method(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let result = client.unimplemented_call(Request::new(Empty {})).await;
|
||||
assertions.push(test_assert!(
|
||||
"call must fail with unimplemented status code",
|
||||
match &result {
|
||||
Err(status) => status.code() == Code::Unimplemented,
|
||||
_ => false,
|
||||
},
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn unimplemented_service(
|
||||
client: &mut UnimplementedClient,
|
||||
assertions: &mut Vec<TestAssertion>,
|
||||
) {
|
||||
let result = client.unimplemented_call(Request::new(Empty {})).await;
|
||||
assertions.push(test_assert!(
|
||||
"call must fail with unimplemented status code",
|
||||
match &result {
|
||||
Err(status) => status.code() == Code::Unimplemented,
|
||||
_ => false,
|
||||
},
|
||||
format!("result={:?}", result)
|
||||
));
|
||||
}
|
||||
|
||||
pub async fn custom_metadata(client: &mut TestClient, assertions: &mut Vec<TestAssertion>) {
|
||||
let key1 = "x-grpc-test-echo-initial";
|
||||
let value1 = MetadataValue::from_str("test_initial_metadata_value").unwrap();
|
||||
let key2 = "x-grpc-test-echo-trailing-bin";
|
||||
let value2 = MetadataValue::from_bytes(&[0xab, 0xab, 0xab]);
|
||||
|
||||
let req = SimpleRequest {
|
||||
response_type: PayloadType::Compressable as i32,
|
||||
response_size: LARGE_RSP_SIZE,
|
||||
payload: Some(crate::client_payload(LARGE_REQ_SIZE)),
|
||||
..Default::default()
|
||||
};
|
||||
let mut req_unary = Request::new(req);
|
||||
req_unary.metadata_mut().insert(key1, value1.clone());
|
||||
req_unary.metadata_mut().insert_bin(key2, value2.clone());
|
||||
|
||||
let stream = stream::iter(vec![make_ping_pong_request(0)]);
|
||||
let mut req_stream = Request::new(stream);
|
||||
req_stream.metadata_mut().insert(key1, value1.clone());
|
||||
req_stream.metadata_mut().insert_bin(key2, value2.clone());
|
||||
|
||||
let response = client
|
||||
.unary_call(req_unary)
|
||||
.await
|
||||
.expect("call should pass.");
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"metadata string must match in unary",
|
||||
response.metadata().get(key1) == Some(&value1),
|
||||
format!("result={:?}", response.metadata().get(key1))
|
||||
));
|
||||
assertions.push(test_assert!(
|
||||
"metadata bin must match in unary",
|
||||
response.metadata().get_bin(key2) == Some(&value2),
|
||||
format!("result={:?}", response.metadata().get_bin(key1))
|
||||
));
|
||||
|
||||
let response = client
|
||||
.full_duplex_call(req_stream)
|
||||
.await
|
||||
.expect("call should pass.");
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"metadata string must match in unary",
|
||||
response.metadata().get(key1) == Some(&value1),
|
||||
format!("result={:?}", response.metadata().get(key1))
|
||||
));
|
||||
|
||||
let mut stream = response.into_inner();
|
||||
|
||||
let trailers = stream.trailers().await.unwrap().unwrap();
|
||||
|
||||
assertions.push(test_assert!(
|
||||
"metadata bin must match in unary",
|
||||
trailers.get_bin(key2) == Some(&value2),
|
||||
format!("result={:?}", trailers.get_bin(key1))
|
||||
));
|
||||
}
|
||||
|
||||
fn make_ping_pong_request(idx: usize) -> StreamingOutputCallRequest {
|
||||
let req_len = REQUEST_LENGTHS[idx];
|
||||
let resp_len = RESPONSE_LENGTHS[idx];
|
||||
StreamingOutputCallRequest {
|
||||
response_parameters: vec![ResponseParameters::with_size(resp_len)],
|
||||
payload: Some(crate::client_payload(req_len as usize)),
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
#![recursion_limit = "256"]
|
||||
|
||||
pub mod client;
|
||||
pub mod server;
|
||||
|
||||
pub mod pb {
|
||||
#![allow(dead_code)]
|
||||
#![allow(unused_imports)]
|
||||
include!(concat!(env!("OUT_DIR"), "/grpc.testing.rs"));
|
||||
}
|
||||
|
||||
use http::header::{HeaderMap, HeaderName, HeaderValue};
|
||||
use http_body::Body;
|
||||
use std::{
|
||||
default, fmt, iter,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
pub fn trace_init() {
|
||||
let sub = tracing_subscriber::FmtSubscriber::builder()
|
||||
.with_env_filter(tracing_subscriber::filter::EnvFilter::from_default_env())
|
||||
.finish();
|
||||
|
||||
let _ = tracing::subscriber::set_global_default(sub);
|
||||
let _ = tracing_log::LogTracer::init();
|
||||
}
|
||||
|
||||
pub fn client_payload(size: usize) -> pb::Payload {
|
||||
pb::Payload {
|
||||
r#type: default::Default::default(),
|
||||
body: iter::repeat(0u8).take(size).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn server_payload(size: usize) -> pb::Payload {
|
||||
pb::Payload {
|
||||
r#type: default::Default::default(),
|
||||
body: iter::repeat(0u8).take(size).collect(),
|
||||
}
|
||||
}
|
||||
|
||||
impl pb::ResponseParameters {
|
||||
fn with_size(size: i32) -> Self {
|
||||
pb::ResponseParameters {
|
||||
size,
|
||||
..Default::default()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn response_length(response: &pb::StreamingOutputCallResponse) -> i32 {
|
||||
match &response.payload {
|
||||
Some(ref payload) => payload.body.len() as i32,
|
||||
None => 0,
|
||||
}
|
||||
}
|
||||
|
||||
fn response_lengths(responses: &Vec<pb::StreamingOutputCallResponse>) -> Vec<i32> {
|
||||
responses.iter().map(&response_length).collect()
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum TestAssertion {
|
||||
Passed {
|
||||
description: &'static str,
|
||||
},
|
||||
Failed {
|
||||
description: &'static str,
|
||||
expression: &'static str,
|
||||
why: Option<String>,
|
||||
},
|
||||
}
|
||||
|
||||
impl TestAssertion {
|
||||
pub fn is_failed(&self) -> bool {
|
||||
match self {
|
||||
TestAssertion::Failed { .. } => true,
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for TestAssertion {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
use console::{style, Emoji};
|
||||
match *self {
|
||||
TestAssertion::Passed { ref description } => write!(
|
||||
f,
|
||||
"{check} {desc}",
|
||||
check = style(Emoji("✔", "+")).green(),
|
||||
desc = style(description).green(),
|
||||
),
|
||||
TestAssertion::Failed {
|
||||
ref description,
|
||||
ref expression,
|
||||
why: Some(ref why),
|
||||
} => write!(
|
||||
f,
|
||||
"{check} {desc}\n in `{exp}`: {why}",
|
||||
check = style(Emoji("✖", "x")).red(),
|
||||
desc = style(description).red(),
|
||||
exp = style(expression).red(),
|
||||
why = style(why).red(),
|
||||
),
|
||||
TestAssertion::Failed {
|
||||
ref description,
|
||||
ref expression,
|
||||
why: None,
|
||||
} => write!(
|
||||
f,
|
||||
"{check} {desc}\n in `{exp}`",
|
||||
check = style(Emoji("✖", "x")).red(),
|
||||
desc = style(description).red(),
|
||||
exp = style(expression).red(),
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[macro_export]
|
||||
macro_rules! test_assert {
|
||||
($description:expr, $assertion:expr) => {
|
||||
if $assertion {
|
||||
crate::TestAssertion::Passed {
|
||||
description: $description,
|
||||
}
|
||||
} else {
|
||||
TestAssertion::Failed {
|
||||
description: $description,
|
||||
expression: stringify!($assertion),
|
||||
why: None,
|
||||
}
|
||||
}
|
||||
};
|
||||
($description:expr, $assertion:expr, $why:expr) => {
|
||||
if $assertion {
|
||||
crate::TestAssertion::Passed {
|
||||
description: $description,
|
||||
}
|
||||
} else {
|
||||
crate::TestAssertion::Failed {
|
||||
description: $description,
|
||||
expression: stringify!($assertion),
|
||||
why: Some($why),
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub struct MergeTrailers<B> {
|
||||
inner: B,
|
||||
trailer: Option<(HeaderName, HeaderValue)>,
|
||||
}
|
||||
|
||||
impl<B> MergeTrailers<B> {
|
||||
pub fn new(inner: B, trailer: Option<(HeaderName, HeaderValue)>) -> Self {
|
||||
Self { inner, trailer }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Body + Unpin> Body for MergeTrailers<B> {
|
||||
type Data = B::Data;
|
||||
type Error = B::Error;
|
||||
|
||||
fn poll_data(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
Pin::new(&mut self.inner).poll_data(cx)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||
Pin::new(&mut self.inner).poll_trailers(cx).map_ok(|h| {
|
||||
h.map(|mut headers| {
|
||||
if let Some((key, value)) = &self.trailer {
|
||||
headers.insert(key.clone(), value.clone());
|
||||
}
|
||||
|
||||
headers
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,161 @@
|
||||
use crate::pb::{self, *};
|
||||
use async_stream::try_stream;
|
||||
use futures_util::{stream, StreamExt, TryStreamExt};
|
||||
use std::pin::Pin;
|
||||
use std::time::Duration;
|
||||
use tonic::{Code, Request, Response, Status};
|
||||
|
||||
pub use pb::testservice_server::TestServiceServer;
|
||||
pub use pb::unimplementedservice_server::UnimplementedServiceServer;
|
||||
|
||||
#[derive(Default, Clone)]
|
||||
pub struct TestService;
|
||||
|
||||
type Result<T> = std::result::Result<Response<T>, Status>;
|
||||
type Streaming<T> = Request<tonic::Streaming<T>>;
|
||||
type Stream<T> = Pin<
|
||||
Box<dyn futures_core::Stream<Item = std::result::Result<T, Status>> + Send + Sync + 'static>,
|
||||
>;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pb::testservice_server::TestService for TestService {
|
||||
async fn empty_call(&self, _request: Request<Empty>) -> Result<Empty> {
|
||||
Ok(Response::new(Empty {}))
|
||||
}
|
||||
|
||||
async fn unary_call(&self, request: Request<SimpleRequest>) -> Result<SimpleResponse> {
|
||||
let req = request.into_inner();
|
||||
|
||||
if let Some(echo_status) = req.response_status {
|
||||
let status = Status::new(Code::from_i32(echo_status.code), echo_status.message);
|
||||
return Err(status);
|
||||
}
|
||||
|
||||
let res_size = if req.response_size >= 0 {
|
||||
req.response_size as usize
|
||||
} else {
|
||||
let status = Status::new(Code::InvalidArgument, "response_size cannot be negative");
|
||||
return Err(status);
|
||||
};
|
||||
|
||||
let res = SimpleResponse {
|
||||
payload: Some(Payload {
|
||||
body: vec![0; res_size],
|
||||
..Default::default()
|
||||
}),
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
async fn cacheable_unary_call(&self, _: Request<SimpleRequest>) -> Result<SimpleResponse> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
type StreamingOutputCallStream = Stream<StreamingOutputCallResponse>;
|
||||
|
||||
async fn streaming_output_call(
|
||||
&self,
|
||||
req: Request<StreamingOutputCallRequest>,
|
||||
) -> Result<Self::StreamingOutputCallStream> {
|
||||
let StreamingOutputCallRequest {
|
||||
response_parameters,
|
||||
..
|
||||
} = req.into_inner();
|
||||
|
||||
let stream = try_stream! {
|
||||
for param in response_parameters {
|
||||
tokio::time::delay_for(Duration::from_micros(param.interval_us as u64)).await;
|
||||
|
||||
let payload = crate::server_payload(param.size as usize);
|
||||
yield StreamingOutputCallResponse { payload: Some(payload) };
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(
|
||||
Box::pin(stream) as Self::StreamingOutputCallStream
|
||||
))
|
||||
}
|
||||
|
||||
async fn streaming_input_call(
|
||||
&self,
|
||||
req: Streaming<StreamingInputCallRequest>,
|
||||
) -> Result<StreamingInputCallResponse> {
|
||||
let mut stream = req.into_inner();
|
||||
|
||||
let mut aggregated_payload_size = 0 as i32;
|
||||
while let Some(msg) = stream.try_next().await? {
|
||||
aggregated_payload_size += msg.payload.unwrap().body.len() as i32;
|
||||
}
|
||||
|
||||
let res = StreamingInputCallResponse {
|
||||
aggregated_payload_size,
|
||||
};
|
||||
|
||||
Ok(Response::new(res))
|
||||
}
|
||||
|
||||
type FullDuplexCallStream = Stream<StreamingOutputCallResponse>;
|
||||
|
||||
async fn full_duplex_call(
|
||||
&self,
|
||||
req: Streaming<StreamingOutputCallRequest>,
|
||||
) -> Result<Self::FullDuplexCallStream> {
|
||||
let mut stream = req.into_inner();
|
||||
|
||||
if let Some(first_msg) = stream.message().await? {
|
||||
if let Some(echo_status) = first_msg.response_status {
|
||||
let status = Status::new(Code::from_i32(echo_status.code), echo_status.message);
|
||||
return Err(status);
|
||||
}
|
||||
|
||||
let single_message = stream::iter(vec![Ok(first_msg)]);
|
||||
let mut stream = single_message.chain(stream);
|
||||
|
||||
let stream = try_stream! {
|
||||
while let Some(msg) = stream.try_next().await? {
|
||||
if let Some(echo_status) = msg.response_status {
|
||||
let status = Status::new(Code::from_i32(echo_status.code), echo_status.message);
|
||||
Err(status)?;
|
||||
}
|
||||
|
||||
for param in msg.response_parameters {
|
||||
tokio::time::delay_for(Duration::from_micros(param.interval_us as u64)).await;
|
||||
|
||||
let payload = crate::server_payload(param.size as usize);
|
||||
yield StreamingOutputCallResponse { payload: Some(payload) };
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Ok(Response::new(Box::pin(stream) as Self::FullDuplexCallStream))
|
||||
} else {
|
||||
let stream = stream::empty();
|
||||
Ok(Response::new(Box::pin(stream) as Self::FullDuplexCallStream))
|
||||
}
|
||||
}
|
||||
|
||||
type HalfDuplexCallStream = Stream<StreamingOutputCallResponse>;
|
||||
|
||||
async fn half_duplex_call(
|
||||
&self,
|
||||
_: Streaming<StreamingOutputCallRequest>,
|
||||
) -> Result<Self::HalfDuplexCallStream> {
|
||||
Err(Status::unimplemented("TODO"))
|
||||
}
|
||||
|
||||
async fn unimplemented_call(&self, _: Request<Empty>) -> Result<Empty> {
|
||||
Err(Status::unimplemented(""))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct UnimplementedService;
|
||||
|
||||
#[tonic::async_trait]
|
||||
impl pb::unimplementedservice_server::UnimplementedService for UnimplementedService {
|
||||
async fn unimplemented_call(&self, _req: Request<Empty>) -> Result<Empty> {
|
||||
Err(Status::unimplemented(""))
|
||||
}
|
||||
}
|
||||
Executable
+59
@@ -0,0 +1,59 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
set -eu
|
||||
set -o pipefail
|
||||
|
||||
set -x
|
||||
|
||||
echo "Running for OS: ${OSTYPE}"
|
||||
|
||||
case "$OSTYPE" in
|
||||
darwin*) OS="darwin"; EXT="" ;;
|
||||
linux*) OS="linux"; EXT="" ;;
|
||||
msys*) OS="windows"; EXT=".exe" ;;
|
||||
*) exit 2 ;;
|
||||
esac
|
||||
|
||||
ARG="${1:-""}"
|
||||
|
||||
(cd interop && cargo build --bins)
|
||||
|
||||
SERVER="interop/bin/server_${OS}_amd64${EXT}"
|
||||
|
||||
# TLS_CA="interop/data/ca.pem"
|
||||
TLS_CRT="interop/data/server1.pem"
|
||||
TLS_KEY="interop/data/server1.key"
|
||||
|
||||
# run the test server
|
||||
./"${SERVER}" ${ARG} --tls_cert_file $TLS_CRT --tls_key_file $TLS_KEY &
|
||||
SERVER_PID=$!
|
||||
echo ":; started grpc-go test server."
|
||||
|
||||
# trap exits to make sure we kill the server process when the script exits,
|
||||
# regardless of why (errors, SIGTERM, etc).
|
||||
trap 'echo ":; killing test server"; kill ${SERVER_PID};' EXIT
|
||||
|
||||
sleep 1
|
||||
|
||||
./target/debug/client \
|
||||
--test_case=empty_unary,large_unary,client_streaming,server_streaming,ping_pong,\
|
||||
empty_stream,status_code_and_message,special_status_message,unimplemented_method,\
|
||||
unimplemented_service,custom_metadata ${ARG}
|
||||
|
||||
echo ":; killing test server"; kill ${SERVER_PID};
|
||||
|
||||
# run the test server
|
||||
./target/debug/server ${ARG} &
|
||||
SERVER_PID=$!
|
||||
echo ":; started tonic test server."
|
||||
|
||||
# trap exits to make sure we kill the server process when the script exits,
|
||||
# regardless of why (errors, SIGTERM, etc).
|
||||
trap 'echo ":; killing test server"; kill ${SERVER_PID};' EXIT
|
||||
|
||||
sleep 1
|
||||
|
||||
./target/debug/client \
|
||||
--test_case=empty_unary,large_unary,client_streaming,server_streaming,ping_pong,\
|
||||
empty_stream,status_code_and_message,special_status_message,unimplemented_method,\
|
||||
unimplemented_service,custom_metadata ${ARG}
|
||||
Reference in New Issue
Block a user