| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056 |
- /*
- * Copyright 2019, gRPC Authors All rights reserved.
- *
- * 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.
- */
- import Dispatch
- import GRPC
- import GRPCInteroperabilityTestModels
- import NIOHPACK
- import struct Foundation.Data
- /// This test verifies that implementations support zero-size messages. Ideally, client
- /// implementations would verify that the request and response were zero bytes serialized, but
- /// this is generally prohibitive to perform, so is not required.
- ///
- /// Server features:
- /// - EmptyCall
- ///
- /// Procedure:
- /// 1. Client calls EmptyCall with the default Empty message
- ///
- /// Client asserts:
- /// - call was successful
- /// - response is non-null
- class EmptyUnary: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let call = client.emptyCall(Grpc_Testing_Empty())
- try waitAndAssertEqual(call.response, Grpc_Testing_Empty())
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies that gRPC requests marked as cacheable use GET verb instead of POST, and
- /// that server sets appropriate cache control headers for the response to be cached by a proxy.
- /// This test requires that the server is behind a caching proxy. Use of current timestamp in the
- /// request prevents accidental cache matches left over from previous tests.
- ///
- /// Server features:
- /// - CacheableUnaryCall
- ///
- /// Procedure:
- /// 1. Client calls CacheableUnaryCall with SimpleRequest request with payload set to current
- /// timestamp. Timestamp format is irrelevant, and resolution is in nanoseconds. Client adds a
- /// x-user-ip header with value 1.2.3.4 to the request. This is done since some proxys such as
- /// GFE will not cache requests from localhost. Client marks the request as cacheable by
- /// setting the cacheable flag in the request context. Longer term this should be driven by
- /// the method option specified in the proto file itself.
- /// 2. Client calls CacheableUnaryCall again immediately with the same request and configuration
- /// as the previous call.
- ///
- /// Client asserts:
- /// - Both calls were successful
- /// - The payload body of both responses is the same.
- class CacheableUnary: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let timestamp = DispatchTime.now().uptimeNanoseconds
- let request = Grpc_Testing_SimpleRequest.withPayload(of: .bytes(of: timestamp))
- let headers: HPACKHeaders = ["x-user-ip": "1.2.3.4"]
- let callOptions = CallOptions(customMetadata: headers, cacheable: true)
- let call1 = client.cacheableUnaryCall(request, callOptions: callOptions)
- let call2 = client.cacheableUnaryCall(request, callOptions: callOptions)
- // The server ignores the request payload so we must not validate against it.
- try waitAndAssertEqual(call1.response.map { $0.payload }, call2.response.map { $0.payload })
- try waitAndAssertEqual(call1.status.map { $0.code }, .ok)
- try waitAndAssertEqual(call2.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies unary calls succeed in sending messages, and touches on flow control (even
- /// if compression is enabled on the channel).
- ///
- /// Server features:
- /// - UnaryCall
- ///
- /// Procedure:
- /// 1. Client calls UnaryCall with:
- /// ```
- /// {
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - call was successful
- /// - response payload body is 314159 bytes in size
- /// - clients are free to assert that the response payload body contents are zero and comparing
- /// the entire response message against a golden response
- class LargeUnary: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let request = Grpc_Testing_SimpleRequest.with { request in
- request.responseSize = 314_159
- request.payload = .zeros(count: 271_828)
- }
- let call = client.unaryCall(request)
- try waitAndAssertEqual(call.response.map { $0.payload }, .zeros(count: 314_159))
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies the client can compress unary messages by sending two unary calls, for
- /// compressed and uncompressed payloads. It also sends an initial probing request to verify
- /// whether the server supports the CompressedRequest feature by checking if the probing call
- /// fails with an `INVALID_ARGUMENT` status.
- ///
- /// Server features:
- /// - UnaryCall
- /// - CompressedRequest
- ///
- /// Procedure:
- /// 1. Client calls UnaryCall with the feature probe, an *uncompressed* message:
- /// ```
- /// {
- /// expect_compressed:{
- /// value: true
- /// }
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- /// 2. Client calls UnaryCall with the *compressed* message:
- /// ```
- /// {
- /// expect_compressed:{
- /// value: true
- /// }
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- /// 3. Client calls UnaryCall with the *uncompressed* message:
- /// ```
- /// {
- /// expect_compressed:{
- /// value: false
- /// }
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - First call failed with `INVALID_ARGUMENT` status.
- /// - Subsequent calls were successful.
- /// - Response payload body is 314159 bytes in size.
- /// - Clients are free to assert that the response payload body contents are zeros and comparing the
- /// entire response message against a golden response.
- class ClientCompressedUnary: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let compressedRequest = Grpc_Testing_SimpleRequest.with { request in
- request.expectCompressed = true
- request.responseSize = 314_159
- request.payload = .zeros(count: 271_828)
- }
- var uncompressedRequest = compressedRequest
- uncompressedRequest.expectCompressed = false
- // For unary RPCs we disable compression at the call level.
- // With compression expected but *disabled*.
- let probe = client.unaryCall(compressedRequest)
- try waitAndAssertEqual(probe.status.map { $0.code }, .invalidArgument)
- // With compression expected and enabled.
- let options =
- CallOptions(
- messageEncoding: .enabled(
- .init(
- forRequests: .gzip,
- decompressionLimit: .absolute(1024 * 1024)
- )
- )
- )
- let compressed = client.unaryCall(compressedRequest, callOptions: options)
- try waitAndAssertEqual(compressed.response.map { $0.payload }, .zeros(count: 314_159))
- try waitAndAssertEqual(compressed.status.map { $0.code }, .ok)
- // With compression not expected and disabled.
- let uncompressed = client.unaryCall(uncompressedRequest)
- try waitAndAssertEqual(uncompressed.response.map { $0.payload }, .zeros(count: 314_159))
- try waitAndAssertEqual(uncompressed.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies the server can compress unary messages. It sends two unary
- /// requests, expecting the server's response to be compressed or not according to
- /// the `response_compressed` boolean.
- ///
- /// Whether compression was actually performed is determined by the compression bit
- /// in the response's message flags. *Note that some languages may not have access
- /// to the message flags, in which case the client will be unable to verify that
- /// the `response_compressed` boolean is obeyed by the server*.
- ///
- ///
- /// Server features:
- /// - UnaryCall
- /// - CompressedResponse
- ///
- /// Procedure:
- /// 1. Client calls UnaryCall with `SimpleRequest`:
- /// ```
- /// {
- /// response_compressed:{
- /// value: true
- /// }
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- /// ```
- /// {
- /// response_compressed:{
- /// value: false
- /// }
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - call was successful
- /// - if supported by the implementation, when `response_compressed` is true, the response MUST have
- /// the compressed message flag set.
- /// - if supported by the implementation, when `response_compressed` is false, the response MUST NOT
- /// have the compressed message flag set.
- /// - response payload body is 314159 bytes in size in both cases.
- /// - clients are free to assert that the response payload body contents are zero and comparing the
- /// entire response message against a golden response
- class ServerCompressedUnary: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let compressedRequest = Grpc_Testing_SimpleRequest.with { request in
- request.responseCompressed = true
- request.responseSize = 314_159
- request.payload = .zeros(count: 271_828)
- }
- let options =
- CallOptions(
- messageEncoding: .enabled(
- .responsesOnly(
- decompressionLimit: .absolute(
- 1024 * 1024
- )
- )
- )
- )
- let compressed = client.unaryCall(compressedRequest, callOptions: options)
- // We can't verify that the compression bit was set, instead we verify that the encoding header
- // was sent by the server. This isn't quite the same since as it can still be set but the
- // compression may be not set.
- try waitAndAssert(compressed.initialMetadata) { headers in
- headers.first(name: "grpc-encoding") != nil
- }
- try waitAndAssertEqual(compressed.response.map { $0.payload }, .zeros(count: 314_159))
- try waitAndAssertEqual(compressed.status.map { $0.code }, .ok)
- var uncompressedRequest = compressedRequest
- uncompressedRequest.responseCompressed.value = false
- let uncompressed = client.unaryCall(uncompressedRequest)
- // We can't check even check for the 'grpc-encoding' header here since it could be set with the
- // compression bit on the message not set.
- try waitAndAssertEqual(uncompressed.response.map { $0.payload }, .zeros(count: 314_159))
- try waitAndAssertEqual(uncompressed.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies that client-only streaming succeeds.
- ///
- /// Server features:
- /// - StreamingInputCall
- ///
- /// Procedure:
- /// 1. Client calls StreamingInputCall
- /// 2. Client sends:
- /// ```
- /// {
- /// payload:{
- /// body: 27182 bytes of zeros
- /// }
- /// }
- /// ```
- /// 3. Client then sends:
- /// ```
- /// {
- /// payload:{
- /// body: 8 bytes of zeros
- /// }
- /// }
- /// ```
- /// 4. Client then sends:
- /// ```
- /// {
- /// payload:{
- /// body: 1828 bytes of zeros
- /// }
- /// }
- /// ```
- /// 5. Client then sends:
- /// ```
- /// {
- /// payload:{
- /// body: 45904 bytes of zeros
- /// }
- /// }
- /// ```
- /// 6. Client half-closes
- ///
- /// Client asserts:
- /// - call was successful
- /// - response aggregated_payload_size is 74922
- class ClientStreaming: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let call = client.streamingInputCall()
- let requests = [27182, 8, 1828, 45904].map { zeros in
- Grpc_Testing_StreamingInputCallRequest.withPayload(of: .zeros(count: zeros))
- }
- call.sendMessages(requests, promise: nil)
- call.sendEnd(promise: nil)
- try waitAndAssertEqual(call.response.map { $0.aggregatedPayloadSize }, 74922)
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies the client can compress requests on per-message basis by performing a
- /// two-request streaming call. It also sends an initial probing request to verify whether the
- /// server supports the `CompressedRequest` feature by checking if the probing call fails with
- /// an `INVALID_ARGUMENT` status.
- ///
- /// Procedure:
- /// 1. Client calls `StreamingInputCall` and sends the following feature-probing
- /// *uncompressed* `StreamingInputCallRequest` message
- ///
- /// ```
- /// {
- /// expect_compressed:{
- /// value: true
- /// }
- /// payload:{
- /// body: 27182 bytes of zeros
- /// }
- /// }
- /// ```
- /// If the call does not fail with `INVALID_ARGUMENT`, the test fails.
- /// Otherwise, we continue.
- ///
- /// 2. Client calls `StreamingInputCall` again, sending the *compressed* message
- ///
- /// ```
- /// {
- /// expect_compressed:{
- /// value: true
- /// }
- /// payload:{
- /// body: 27182 bytes of zeros
- /// }
- /// }
- /// ```
- ///
- /// 3. And finally, the *uncompressed* message
- /// ```
- /// {
- /// expect_compressed:{
- /// value: false
- /// }
- /// payload:{
- /// body: 45904 bytes of zeros
- /// }
- /// }
- /// ```
- ///
- /// 4. Client half-closes
- ///
- /// Client asserts:
- /// - First call fails with `INVALID_ARGUMENT`.
- /// - Next calls succeeds.
- /// - Response aggregated payload size is 73086.
- class ClientCompressedStreaming: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- // Does the server support this test? To find out we need to send an uncompressed probe. However
- // we need to disable compression at the RPC level as we don't have access to whether the
- // compression byte is set on messages. As such the corresponding code in the service
- // implementation checks against the 'grpc-encoding' header as a best guess. Disabling
- // compression here will stop that header from being sent.
- let probe = client.streamingInputCall()
- let probeRequest: Grpc_Testing_StreamingInputCallRequest = .with { request in
- request.expectCompressed = true
- request.payload = .zeros(count: 27182)
- }
- // Compression is disabled at the RPC level.
- probe.sendMessage(probeRequest, promise: nil)
- probe.sendEnd(promise: nil)
- // We *expect* invalid argument here. If not then the server doesn't support this test.
- try waitAndAssertEqual(probe.status.map { $0.code }, .invalidArgument)
- // Now for the actual test.
- // The first message is identical to the probe message, we'll reuse that.
- // The second should not be compressed.
- let secondMessage: Grpc_Testing_StreamingInputCallRequest = .with { request in
- request.expectCompressed = false
- request.payload = .zeros(count: 45904)
- }
- let options =
- CallOptions(
- messageEncoding: .enabled(
- .init(
- forRequests: .gzip,
- decompressionLimit: .ratio(10)
- )
- )
- )
- let streaming = client.streamingInputCall(callOptions: options)
- streaming.sendMessage(probeRequest, compression: .enabled, promise: nil)
- streaming.sendMessage(secondMessage, compression: .disabled, promise: nil)
- streaming.sendEnd(promise: nil)
- try waitAndAssertEqual(streaming.response.map { $0.aggregatedPayloadSize }, 73086)
- try waitAndAssertEqual(streaming.status.map { $0.code }, .ok)
- }
- }
- /// This test verifies that server-only streaming succeeds.
- ///
- /// Server features:
- /// - StreamingOutputCall
- ///
- /// Procedure:
- /// 1. Client calls StreamingOutputCall with StreamingOutputCallRequest:
- /// ```
- /// {
- /// response_parameters:{
- /// size: 31415
- /// }
- /// response_parameters:{
- /// size: 9
- /// }
- /// response_parameters:{
- /// size: 2653
- /// }
- /// response_parameters:{
- /// size: 58979
- /// }
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - call was successful
- /// - exactly four responses
- /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979
- /// - clients are free to assert that the response payload body contents are zero and
- /// comparing the entire response messages against golden responses
- class ServerStreaming: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let responseSizes = [31415, 9, 2653, 58979]
- let request = Grpc_Testing_StreamingOutputCallRequest.with { request in
- request.responseParameters = responseSizes.map { .size($0) }
- }
- var payloads: [Grpc_Testing_Payload] = []
- let call = client.streamingOutputCall(request) { response in
- payloads.append(response.payload)
- }
- // Wait for the status first to ensure we've finished collecting responses.
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- try assertEqual(payloads, responseSizes.map { .zeros(count: $0) })
- }
- }
- /// This test verifies that the server can compress streaming messages and disable compression on
- /// individual messages, expecting the server's response to be compressed or not according to the
- /// `response_compressed` boolean.
- ///
- /// Whether compression was actually performed is determined by the compression bit in the
- /// response's message flags. *Note that some languages may not have access to the message flags, in
- /// which case the client will be unable to verify that the `response_compressed` boolean is obeyed
- /// by the server*.
- ///
- /// Server features:
- /// - StreamingOutputCall
- /// - CompressedResponse
- ///
- /// Procedure:
- /// 1. Client calls StreamingOutputCall with `StreamingOutputCallRequest`:
- /// ```
- /// {
- /// response_parameters:{
- /// compressed: {
- /// value: true
- /// }
- /// size: 31415
- /// }
- /// response_parameters:{
- /// compressed: {
- /// value: false
- /// }
- /// size: 92653
- /// }
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - call was successful
- /// - exactly two responses
- /// - if supported by the implementation, when `response_compressed` is false, the response's
- /// messages MUST NOT have the compressed message flag set.
- /// - if supported by the implementation, when `response_compressed` is true, the response's
- /// messages MUST have the compressed message flag set.
- /// - response payload bodies are sized (in order): 31415, 92653
- /// - clients are free to assert that the response payload body contents are zero and comparing the
- /// entire response messages against golden responses
- class ServerCompressedStreaming: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let request: Grpc_Testing_StreamingOutputCallRequest = .with { request in
- request.responseParameters = [
- .with {
- $0.compressed = true
- $0.size = 31415
- },
- .with {
- $0.compressed = false
- $0.size = 92653
- },
- ]
- }
- let options =
- CallOptions(
- messageEncoding: .enabled(
- .responsesOnly(
- decompressionLimit: .absolute(
- 1024 * 1024
- )
- )
- )
- )
- var payloads: [Grpc_Testing_Payload] = []
- let rpc = client.streamingOutputCall(request, callOptions: options) { response in
- payloads.append(response.payload)
- }
- // We can't verify that the compression bit was set, instead we verify that the encoding header
- // was sent by the server. This isn't quite the same since as it can still be set but the
- // compression may be not set.
- try waitAndAssert(rpc.initialMetadata) { headers in
- headers.first(name: "grpc-encoding") != nil
- }
- let responseSizes = [31415, 92653]
- // Wait for the status first to ensure we've finished collecting responses.
- try waitAndAssertEqual(rpc.status.map { $0.code }, .ok)
- try assertEqual(payloads, responseSizes.map { .zeros(count: $0) })
- }
- }
- /// This test verifies that full duplex bidi is supported.
- ///
- /// Server features:
- /// - FullDuplexCall
- ///
- /// Procedure:
- /// 1. Client calls FullDuplexCall with:
- /// ```
- /// {
- /// response_parameters:{
- /// size: 31415
- /// }
- /// payload:{
- /// body: 27182 bytes of zeros
- /// }
- /// }
- /// ```
- /// 2. After getting a reply, it sends:
- /// ```
- /// {
- /// response_parameters:{
- /// size: 9
- /// }
- /// payload:{
- /// body: 8 bytes of zeros
- /// }
- /// }
- /// ```
- /// 3. After getting a reply, it sends:
- /// ```
- /// {
- /// response_parameters:{
- /// size: 2653
- /// }
- /// payload:{
- /// body: 1828 bytes of zeros
- /// }
- /// }
- /// ```
- /// 4. After getting a reply, it sends:
- /// ```
- /// {
- /// response_parameters:{
- /// size: 58979
- /// }
- /// payload:{
- /// body: 45904 bytes of zeros
- /// }
- /// }
- /// ```
- /// 5. After getting a reply, client half-closes
- ///
- /// Client asserts:
- /// - call was successful
- /// - exactly four responses
- /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979
- /// - clients are free to assert that the response payload body contents are zero and
- /// comparing the entire response messages against golden responses
- class PingPong: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let requestSizes = [27182, 8, 1828, 45904]
- let responseSizes = [31415, 9, 2653, 58979]
- let responseReceived = DispatchSemaphore(value: 0)
- var payloads: [Grpc_Testing_Payload] = []
- let call = client.fullDuplexCall { response in
- payloads.append(response.payload)
- responseReceived.signal()
- }
- try zip(requestSizes, responseSizes).map { requestSize, responseSize in
- Grpc_Testing_StreamingOutputCallRequest.with { request in
- request.payload = .zeros(count: requestSize)
- request.responseParameters = [.size(responseSize)]
- }
- }.forEach { request in
- call.sendMessage(request, promise: nil)
- try assertEqual(responseReceived.wait(timeout: .now() + .seconds(1)), .success)
- }
- call.sendEnd(promise: nil)
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- try assertEqual(payloads, responseSizes.map { .zeros(count: $0) })
- }
- }
- /// This test verifies that streams support having zero-messages in both directions.
- ///
- /// Server features:
- /// - FullDuplexCall
- ///
- /// Procedure:
- /// 1. Client calls FullDuplexCall and then half-closes
- ///
- /// Client asserts:
- /// - call was successful
- /// - exactly zero responses
- class EmptyStream: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- var responses: [Grpc_Testing_StreamingOutputCallResponse] = []
- let call = client.fullDuplexCall { response in
- responses.append(response)
- }
- try call.sendEnd().wait()
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- try assertEqual(responses, [])
- }
- }
- /// This test verifies that custom metadata in either binary or ascii format can be sent as
- /// initial-metadata by the client and as both initial- and trailing-metadata by the server.
- ///
- /// Server features:
- /// - UnaryCall
- /// - FullDuplexCall
- /// - Echo Metadata
- ///
- /// Procedure:
- /// 1. The client attaches custom metadata with the following keys and values
- /// to a UnaryCall with request:
- /// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
- /// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab
- /// ```
- /// {
- /// response_size: 314159
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- /// 2. The client attaches custom metadata with the following keys and values
- /// to a FullDuplexCall with request:
- /// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
- /// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab
- /// ```
- /// {
- /// response_parameters:{
- /// size: 314159
- /// }
- /// payload:{
- /// body: 271828 bytes of zeros
- /// }
- /// }
- /// ```
- /// and then half-closes
- ///
- /// Client asserts:
- /// - call was successful
- /// - metadata with key "x-grpc-test-echo-initial" and value "test_initial_metadata_value" is
- /// received in the initial metadata for calls in Procedure steps 1 and 2.
- /// - metadata with key "x-grpc-test-echo-trailing-bin" and value 0xababab is received in the
- /// trailing metadata for calls in Procedure steps 1 and 2.
- class CustomMetadata: InteroperabilityTest {
- let initialMetadataName = "x-grpc-test-echo-initial"
- let initialMetadataValue = "test_initial_metadata_value"
- let trailingMetadataName = "x-grpc-test-echo-trailing-bin"
- let trailingMetadataValue = Data([0xAB, 0xAB, 0xAB]).base64EncodedString()
- func checkMetadata<SpecificClientCall>(call: SpecificClientCall) throws
- where SpecificClientCall: ClientCall {
- let initialName = call.initialMetadata.map { $0[self.initialMetadataName] }
- try waitAndAssertEqual(initialName, [self.initialMetadataValue])
- let trailingName = call.trailingMetadata.map { $0[self.trailingMetadataName] }
- try waitAndAssertEqual(trailingName, [self.trailingMetadataValue])
- try waitAndAssertEqual(call.status.map { $0.code }, .ok)
- }
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let unaryRequest = Grpc_Testing_SimpleRequest.with { request in
- request.responseSize = 314_159
- request.payload = .zeros(count: 217_828)
- }
- let customMetadata: HPACKHeaders = [
- self.initialMetadataName: self.initialMetadataValue,
- self.trailingMetadataName: self.trailingMetadataValue,
- ]
- let callOptions = CallOptions(customMetadata: customMetadata)
- let unaryCall = client.unaryCall(unaryRequest, callOptions: callOptions)
- try self.checkMetadata(call: unaryCall)
- let duplexCall = client.fullDuplexCall(callOptions: callOptions) { _ in }
- let duplexRequest = Grpc_Testing_StreamingOutputCallRequest.with { request in
- request.responseParameters = [.size(314_159)]
- request.payload = .zeros(count: 271_828)
- }
- duplexCall.sendMessage(duplexRequest, promise: nil)
- duplexCall.sendEnd(promise: nil)
- try self.checkMetadata(call: duplexCall)
- }
- }
- /// This test verifies unary calls succeed in sending messages, and propagate back status code and
- /// message sent along with the messages.
- ///
- /// Server features:
- /// - UnaryCall
- /// - FullDuplexCall
- /// - Echo Status
- ///
- /// Procedure:
- /// 1. Client calls UnaryCall with:
- /// ```
- /// {
- /// response_status:{
- /// code: 2
- /// message: "test status message"
- /// }
- /// }
- /// ```
- /// 2. Client calls FullDuplexCall with:
- /// ```
- /// {
- /// response_status:{
- /// code: 2
- /// message: "test status message"
- /// }
- /// }
- /// ```
- /// 3. and then half-closes
- ///
- /// Client asserts:
- /// - received status code is the same as the sent code for both Procedure steps 1 and 2
- /// - received status message is the same as the sent message for both Procedure steps 1 and 2
- class StatusCodeAndMessage: InteroperabilityTest {
- let expectedCode = 2
- let expectedMessage = "test status message"
- func checkStatus<SpecificClientCall>(call: SpecificClientCall) throws
- where SpecificClientCall: ClientCall {
- try waitAndAssertEqual(call.status.map { $0.code.rawValue }, self.expectedCode)
- try waitAndAssertEqual(call.status.map { $0.message }, self.expectedMessage)
- }
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let echoStatus = Grpc_Testing_EchoStatus(
- code: Int32(self.expectedCode),
- message: self.expectedMessage
- )
- let unaryCall = client.unaryCall(.withStatus(of: echoStatus))
- try self.checkStatus(call: unaryCall)
- var responses: [Grpc_Testing_StreamingOutputCallResponse] = []
- let duplexCall = client.fullDuplexCall { response in
- responses.append(response)
- }
- duplexCall.sendMessage(.withStatus(of: echoStatus), promise: nil)
- duplexCall.sendEnd(promise: nil)
- try self.checkStatus(call: duplexCall)
- try assertEqual(responses, [])
- }
- }
- /// This test verifies Unicode and whitespace is correctly processed in status message. "\t" is
- /// horizontal tab. "\r" is carriage return. "\n" is line feed.
- ///
- /// Server features:
- /// - UnaryCall
- /// - Echo Status
- ///
- /// Procedure:
- /// 1. Client calls UnaryCall with:
- /// ```
- /// {
- /// response_status:{
- /// code: 2
- /// message: "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
- /// }
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - received status code is the same as the sent code for Procedure step 1
- /// - received status message is the same as the sent message for Procedure step 1, including all
- /// whitespace characters
- class SpecialStatusMessage: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let code = 2
- let message = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
- let call = client.unaryCall(.withStatus(of: .init(code: Int32(code), message: message)))
- try waitAndAssertEqual(call.status.map { $0.code.rawValue }, code)
- try waitAndAssertEqual(call.status.map { $0.message }, message)
- }
- }
- /// This test verifies that calling an unimplemented RPC method returns the UNIMPLEMENTED status
- /// code.
- ///
- /// Server features: N/A
- ///
- /// Procedure:
- /// 1. Client calls grpc.testing.TestService/UnimplementedCall with an empty request (defined as
- /// grpc.testing.Empty):
- /// ```
- /// {
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - received status code is 12 (UNIMPLEMENTED)
- class UnimplementedMethod: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let call = client.unimplementedCall(Grpc_Testing_Empty())
- try waitAndAssertEqual(call.status.map { $0.code }, .unimplemented)
- }
- }
- /// This test verifies calling an unimplemented server returns the UNIMPLEMENTED status code.
- ///
- /// Server features: N/A
- ///
- /// Procedure:
- /// 1. Client calls grpc.testing.UnimplementedService/UnimplementedCall with an empty request
- /// (defined as grpc.testing.Empty):
- /// ```
- /// {
- /// }
- /// ```
- ///
- /// Client asserts:
- /// - received status code is 12 (UNIMPLEMENTED)
- class UnimplementedService: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_UnimplementedServiceNIOClient(channel: connection)
- let call = client.unimplementedCall(Grpc_Testing_Empty())
- try waitAndAssertEqual(call.status.map { $0.code }, .unimplemented)
- }
- }
- /// This test verifies that a request can be cancelled after metadata has been sent but before
- /// payloads are sent.
- ///
- /// Server features:
- /// - StreamingInputCall
- ///
- /// Procedure:
- /// 1. Client starts StreamingInputCall
- /// 2. Client immediately cancels request
- ///
- /// Client asserts:
- /// - Call completed with status CANCELLED
- class CancelAfterBegin: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let call = client.streamingInputCall()
- call.cancel(promise: nil)
- try waitAndAssertEqual(call.status.map { $0.code }, .cancelled)
- }
- }
- /// This test verifies that a request can be cancelled after receiving a message from the server.
- ///
- /// Server features:
- /// - FullDuplexCall
- ///
- /// Procedure:
- /// 1. Client starts FullDuplexCall with
- /// ```
- /// {
- /// response_parameters:{
- /// size: 31415
- /// }
- /// payload:{
- /// body: 27182 bytes of zeros
- /// }
- /// }
- /// ```
- /// 2. After receiving a response, client cancels request
- ///
- /// Client asserts:
- /// - Call completed with status CANCELLED
- class CancelAfterFirstResponse: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let promise = connection.eventLoop.makePromise(of: Void.self)
- let call = client.fullDuplexCall { _ in
- promise.succeed(())
- }
- promise.futureResult.whenSuccess {
- call.cancel(promise: nil)
- }
- let request = Grpc_Testing_StreamingOutputCallRequest.with { request in
- request.responseParameters = [.size(31415)]
- request.payload = .zeros(count: 27182)
- }
- call.sendMessage(request, promise: nil)
- try waitAndAssertEqual(call.status.map { $0.code }, .cancelled)
- }
- }
- /// This test verifies that an RPC request whose lifetime exceeds its configured timeout value
- /// will end with the DeadlineExceeded status.
- ///
- /// Server features:
- /// - FullDuplexCall
- ///
- /// Procedure:
- /// 1. Client calls FullDuplexCall with the following request and sets its timeout to 1ms
- /// ```
- /// {
- /// payload:{
- /// body: 27182 bytes of zeros
- /// }
- /// }
- /// ```
- /// 2. Client waits
- ///
- /// Client asserts:
- /// - Call completed with status DEADLINE_EXCEEDED.
- class TimeoutOnSleepingServer: InteroperabilityTest {
- func run(using connection: ClientConnection) throws {
- let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
- let callOptions = CallOptions(timeLimit: .timeout(.milliseconds(1)))
- let call = client.fullDuplexCall(callOptions: callOptions) { _ in }
- try waitAndAssertEqual(call.status.map { $0.code }, .deadlineExceeded)
- }
- }
|