InteroperabilityTestCases.swift 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056
  1. /*
  2. * Copyright 2019, gRPC Authors All rights reserved.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. import Dispatch
  17. import GRPC
  18. import GRPCInteroperabilityTestModels
  19. import NIOHPACK
  20. import struct Foundation.Data
  21. /// This test verifies that implementations support zero-size messages. Ideally, client
  22. /// implementations would verify that the request and response were zero bytes serialized, but
  23. /// this is generally prohibitive to perform, so is not required.
  24. ///
  25. /// Server features:
  26. /// - EmptyCall
  27. ///
  28. /// Procedure:
  29. /// 1. Client calls EmptyCall with the default Empty message
  30. ///
  31. /// Client asserts:
  32. /// - call was successful
  33. /// - response is non-null
  34. class EmptyUnary: InteroperabilityTest {
  35. func run(using connection: ClientConnection) throws {
  36. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  37. let call = client.emptyCall(Grpc_Testing_Empty())
  38. try waitAndAssertEqual(call.response, Grpc_Testing_Empty())
  39. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  40. }
  41. }
  42. /// This test verifies that gRPC requests marked as cacheable use GET verb instead of POST, and
  43. /// that server sets appropriate cache control headers for the response to be cached by a proxy.
  44. /// This test requires that the server is behind a caching proxy. Use of current timestamp in the
  45. /// request prevents accidental cache matches left over from previous tests.
  46. ///
  47. /// Server features:
  48. /// - CacheableUnaryCall
  49. ///
  50. /// Procedure:
  51. /// 1. Client calls CacheableUnaryCall with SimpleRequest request with payload set to current
  52. /// timestamp. Timestamp format is irrelevant, and resolution is in nanoseconds. Client adds a
  53. /// x-user-ip header with value 1.2.3.4 to the request. This is done since some proxys such as
  54. /// GFE will not cache requests from localhost. Client marks the request as cacheable by
  55. /// setting the cacheable flag in the request context. Longer term this should be driven by
  56. /// the method option specified in the proto file itself.
  57. /// 2. Client calls CacheableUnaryCall again immediately with the same request and configuration
  58. /// as the previous call.
  59. ///
  60. /// Client asserts:
  61. /// - Both calls were successful
  62. /// - The payload body of both responses is the same.
  63. class CacheableUnary: InteroperabilityTest {
  64. func run(using connection: ClientConnection) throws {
  65. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  66. let timestamp = DispatchTime.now().uptimeNanoseconds
  67. let request = Grpc_Testing_SimpleRequest.withPayload(of: .bytes(of: timestamp))
  68. let headers: HPACKHeaders = ["x-user-ip": "1.2.3.4"]
  69. let callOptions = CallOptions(customMetadata: headers, cacheable: true)
  70. let call1 = client.cacheableUnaryCall(request, callOptions: callOptions)
  71. let call2 = client.cacheableUnaryCall(request, callOptions: callOptions)
  72. // The server ignores the request payload so we must not validate against it.
  73. try waitAndAssertEqual(call1.response.map { $0.payload }, call2.response.map { $0.payload })
  74. try waitAndAssertEqual(call1.status.map { $0.code }, .ok)
  75. try waitAndAssertEqual(call2.status.map { $0.code }, .ok)
  76. }
  77. }
  78. /// This test verifies unary calls succeed in sending messages, and touches on flow control (even
  79. /// if compression is enabled on the channel).
  80. ///
  81. /// Server features:
  82. /// - UnaryCall
  83. ///
  84. /// Procedure:
  85. /// 1. Client calls UnaryCall with:
  86. /// ```
  87. /// {
  88. /// response_size: 314159
  89. /// payload:{
  90. /// body: 271828 bytes of zeros
  91. /// }
  92. /// }
  93. /// ```
  94. ///
  95. /// Client asserts:
  96. /// - call was successful
  97. /// - response payload body is 314159 bytes in size
  98. /// - clients are free to assert that the response payload body contents are zero and comparing
  99. /// the entire response message against a golden response
  100. class LargeUnary: InteroperabilityTest {
  101. func run(using connection: ClientConnection) throws {
  102. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  103. let request = Grpc_Testing_SimpleRequest.with { request in
  104. request.responseSize = 314_159
  105. request.payload = .zeros(count: 271_828)
  106. }
  107. let call = client.unaryCall(request)
  108. try waitAndAssertEqual(call.response.map { $0.payload }, .zeros(count: 314_159))
  109. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  110. }
  111. }
  112. /// This test verifies the client can compress unary messages by sending two unary calls, for
  113. /// compressed and uncompressed payloads. It also sends an initial probing request to verify
  114. /// whether the server supports the CompressedRequest feature by checking if the probing call
  115. /// fails with an `INVALID_ARGUMENT` status.
  116. ///
  117. /// Server features:
  118. /// - UnaryCall
  119. /// - CompressedRequest
  120. ///
  121. /// Procedure:
  122. /// 1. Client calls UnaryCall with the feature probe, an *uncompressed* message:
  123. /// ```
  124. /// {
  125. /// expect_compressed:{
  126. /// value: true
  127. /// }
  128. /// response_size: 314159
  129. /// payload:{
  130. /// body: 271828 bytes of zeros
  131. /// }
  132. /// }
  133. /// ```
  134. /// 2. Client calls UnaryCall with the *compressed* message:
  135. /// ```
  136. /// {
  137. /// expect_compressed:{
  138. /// value: true
  139. /// }
  140. /// response_size: 314159
  141. /// payload:{
  142. /// body: 271828 bytes of zeros
  143. /// }
  144. /// }
  145. /// ```
  146. /// 3. Client calls UnaryCall with the *uncompressed* message:
  147. /// ```
  148. /// {
  149. /// expect_compressed:{
  150. /// value: false
  151. /// }
  152. /// response_size: 314159
  153. /// payload:{
  154. /// body: 271828 bytes of zeros
  155. /// }
  156. /// }
  157. /// ```
  158. ///
  159. /// Client asserts:
  160. /// - First call failed with `INVALID_ARGUMENT` status.
  161. /// - Subsequent calls were successful.
  162. /// - Response payload body is 314159 bytes in size.
  163. /// - Clients are free to assert that the response payload body contents are zeros and comparing the
  164. /// entire response message against a golden response.
  165. class ClientCompressedUnary: InteroperabilityTest {
  166. func run(using connection: ClientConnection) throws {
  167. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  168. let compressedRequest = Grpc_Testing_SimpleRequest.with { request in
  169. request.expectCompressed = true
  170. request.responseSize = 314_159
  171. request.payload = .zeros(count: 271_828)
  172. }
  173. var uncompressedRequest = compressedRequest
  174. uncompressedRequest.expectCompressed = false
  175. // For unary RPCs we disable compression at the call level.
  176. // With compression expected but *disabled*.
  177. let probe = client.unaryCall(compressedRequest)
  178. try waitAndAssertEqual(probe.status.map { $0.code }, .invalidArgument)
  179. // With compression expected and enabled.
  180. let options =
  181. CallOptions(
  182. messageEncoding: .enabled(
  183. .init(
  184. forRequests: .gzip,
  185. decompressionLimit: .absolute(1024 * 1024)
  186. )
  187. )
  188. )
  189. let compressed = client.unaryCall(compressedRequest, callOptions: options)
  190. try waitAndAssertEqual(compressed.response.map { $0.payload }, .zeros(count: 314_159))
  191. try waitAndAssertEqual(compressed.status.map { $0.code }, .ok)
  192. // With compression not expected and disabled.
  193. let uncompressed = client.unaryCall(uncompressedRequest)
  194. try waitAndAssertEqual(uncompressed.response.map { $0.payload }, .zeros(count: 314_159))
  195. try waitAndAssertEqual(uncompressed.status.map { $0.code }, .ok)
  196. }
  197. }
  198. /// This test verifies the server can compress unary messages. It sends two unary
  199. /// requests, expecting the server's response to be compressed or not according to
  200. /// the `response_compressed` boolean.
  201. ///
  202. /// Whether compression was actually performed is determined by the compression bit
  203. /// in the response's message flags. *Note that some languages may not have access
  204. /// to the message flags, in which case the client will be unable to verify that
  205. /// the `response_compressed` boolean is obeyed by the server*.
  206. ///
  207. ///
  208. /// Server features:
  209. /// - UnaryCall
  210. /// - CompressedResponse
  211. ///
  212. /// Procedure:
  213. /// 1. Client calls UnaryCall with `SimpleRequest`:
  214. /// ```
  215. /// {
  216. /// response_compressed:{
  217. /// value: true
  218. /// }
  219. /// response_size: 314159
  220. /// payload:{
  221. /// body: 271828 bytes of zeros
  222. /// }
  223. /// }
  224. /// ```
  225. /// ```
  226. /// {
  227. /// response_compressed:{
  228. /// value: false
  229. /// }
  230. /// response_size: 314159
  231. /// payload:{
  232. /// body: 271828 bytes of zeros
  233. /// }
  234. /// }
  235. /// ```
  236. ///
  237. /// Client asserts:
  238. /// - call was successful
  239. /// - if supported by the implementation, when `response_compressed` is true, the response MUST have
  240. /// the compressed message flag set.
  241. /// - if supported by the implementation, when `response_compressed` is false, the response MUST NOT
  242. /// have the compressed message flag set.
  243. /// - response payload body is 314159 bytes in size in both cases.
  244. /// - clients are free to assert that the response payload body contents are zero and comparing the
  245. /// entire response message against a golden response
  246. class ServerCompressedUnary: InteroperabilityTest {
  247. func run(using connection: ClientConnection) throws {
  248. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  249. let compressedRequest = Grpc_Testing_SimpleRequest.with { request in
  250. request.responseCompressed = true
  251. request.responseSize = 314_159
  252. request.payload = .zeros(count: 271_828)
  253. }
  254. let options =
  255. CallOptions(
  256. messageEncoding: .enabled(
  257. .responsesOnly(
  258. decompressionLimit: .absolute(
  259. 1024 * 1024
  260. )
  261. )
  262. )
  263. )
  264. let compressed = client.unaryCall(compressedRequest, callOptions: options)
  265. // We can't verify that the compression bit was set, instead we verify that the encoding header
  266. // was sent by the server. This isn't quite the same since as it can still be set but the
  267. // compression may be not set.
  268. try waitAndAssert(compressed.initialMetadata) { headers in
  269. headers.first(name: "grpc-encoding") != nil
  270. }
  271. try waitAndAssertEqual(compressed.response.map { $0.payload }, .zeros(count: 314_159))
  272. try waitAndAssertEqual(compressed.status.map { $0.code }, .ok)
  273. var uncompressedRequest = compressedRequest
  274. uncompressedRequest.responseCompressed.value = false
  275. let uncompressed = client.unaryCall(uncompressedRequest)
  276. // We can't check even check for the 'grpc-encoding' header here since it could be set with the
  277. // compression bit on the message not set.
  278. try waitAndAssertEqual(uncompressed.response.map { $0.payload }, .zeros(count: 314_159))
  279. try waitAndAssertEqual(uncompressed.status.map { $0.code }, .ok)
  280. }
  281. }
  282. /// This test verifies that client-only streaming succeeds.
  283. ///
  284. /// Server features:
  285. /// - StreamingInputCall
  286. ///
  287. /// Procedure:
  288. /// 1. Client calls StreamingInputCall
  289. /// 2. Client sends:
  290. /// ```
  291. /// {
  292. /// payload:{
  293. /// body: 27182 bytes of zeros
  294. /// }
  295. /// }
  296. /// ```
  297. /// 3. Client then sends:
  298. /// ```
  299. /// {
  300. /// payload:{
  301. /// body: 8 bytes of zeros
  302. /// }
  303. /// }
  304. /// ```
  305. /// 4. Client then sends:
  306. /// ```
  307. /// {
  308. /// payload:{
  309. /// body: 1828 bytes of zeros
  310. /// }
  311. /// }
  312. /// ```
  313. /// 5. Client then sends:
  314. /// ```
  315. /// {
  316. /// payload:{
  317. /// body: 45904 bytes of zeros
  318. /// }
  319. /// }
  320. /// ```
  321. /// 6. Client half-closes
  322. ///
  323. /// Client asserts:
  324. /// - call was successful
  325. /// - response aggregated_payload_size is 74922
  326. class ClientStreaming: InteroperabilityTest {
  327. func run(using connection: ClientConnection) throws {
  328. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  329. let call = client.streamingInputCall()
  330. let requests = [27182, 8, 1828, 45904].map { zeros in
  331. Grpc_Testing_StreamingInputCallRequest.withPayload(of: .zeros(count: zeros))
  332. }
  333. call.sendMessages(requests, promise: nil)
  334. call.sendEnd(promise: nil)
  335. try waitAndAssertEqual(call.response.map { $0.aggregatedPayloadSize }, 74922)
  336. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  337. }
  338. }
  339. /// This test verifies the client can compress requests on per-message basis by performing a
  340. /// two-request streaming call. It also sends an initial probing request to verify whether the
  341. /// server supports the `CompressedRequest` feature by checking if the probing call fails with
  342. /// an `INVALID_ARGUMENT` status.
  343. ///
  344. /// Procedure:
  345. /// 1. Client calls `StreamingInputCall` and sends the following feature-probing
  346. /// *uncompressed* `StreamingInputCallRequest` message
  347. ///
  348. /// ```
  349. /// {
  350. /// expect_compressed:{
  351. /// value: true
  352. /// }
  353. /// payload:{
  354. /// body: 27182 bytes of zeros
  355. /// }
  356. /// }
  357. /// ```
  358. /// If the call does not fail with `INVALID_ARGUMENT`, the test fails.
  359. /// Otherwise, we continue.
  360. ///
  361. /// 2. Client calls `StreamingInputCall` again, sending the *compressed* message
  362. ///
  363. /// ```
  364. /// {
  365. /// expect_compressed:{
  366. /// value: true
  367. /// }
  368. /// payload:{
  369. /// body: 27182 bytes of zeros
  370. /// }
  371. /// }
  372. /// ```
  373. ///
  374. /// 3. And finally, the *uncompressed* message
  375. /// ```
  376. /// {
  377. /// expect_compressed:{
  378. /// value: false
  379. /// }
  380. /// payload:{
  381. /// body: 45904 bytes of zeros
  382. /// }
  383. /// }
  384. /// ```
  385. ///
  386. /// 4. Client half-closes
  387. ///
  388. /// Client asserts:
  389. /// - First call fails with `INVALID_ARGUMENT`.
  390. /// - Next calls succeeds.
  391. /// - Response aggregated payload size is 73086.
  392. class ClientCompressedStreaming: InteroperabilityTest {
  393. func run(using connection: ClientConnection) throws {
  394. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  395. // Does the server support this test? To find out we need to send an uncompressed probe. However
  396. // we need to disable compression at the RPC level as we don't have access to whether the
  397. // compression byte is set on messages. As such the corresponding code in the service
  398. // implementation checks against the 'grpc-encoding' header as a best guess. Disabling
  399. // compression here will stop that header from being sent.
  400. let probe = client.streamingInputCall()
  401. let probeRequest: Grpc_Testing_StreamingInputCallRequest = .with { request in
  402. request.expectCompressed = true
  403. request.payload = .zeros(count: 27182)
  404. }
  405. // Compression is disabled at the RPC level.
  406. probe.sendMessage(probeRequest, promise: nil)
  407. probe.sendEnd(promise: nil)
  408. // We *expect* invalid argument here. If not then the server doesn't support this test.
  409. try waitAndAssertEqual(probe.status.map { $0.code }, .invalidArgument)
  410. // Now for the actual test.
  411. // The first message is identical to the probe message, we'll reuse that.
  412. // The second should not be compressed.
  413. let secondMessage: Grpc_Testing_StreamingInputCallRequest = .with { request in
  414. request.expectCompressed = false
  415. request.payload = .zeros(count: 45904)
  416. }
  417. let options =
  418. CallOptions(
  419. messageEncoding: .enabled(
  420. .init(
  421. forRequests: .gzip,
  422. decompressionLimit: .ratio(10)
  423. )
  424. )
  425. )
  426. let streaming = client.streamingInputCall(callOptions: options)
  427. streaming.sendMessage(probeRequest, compression: .enabled, promise: nil)
  428. streaming.sendMessage(secondMessage, compression: .disabled, promise: nil)
  429. streaming.sendEnd(promise: nil)
  430. try waitAndAssertEqual(streaming.response.map { $0.aggregatedPayloadSize }, 73086)
  431. try waitAndAssertEqual(streaming.status.map { $0.code }, .ok)
  432. }
  433. }
  434. /// This test verifies that server-only streaming succeeds.
  435. ///
  436. /// Server features:
  437. /// - StreamingOutputCall
  438. ///
  439. /// Procedure:
  440. /// 1. Client calls StreamingOutputCall with StreamingOutputCallRequest:
  441. /// ```
  442. /// {
  443. /// response_parameters:{
  444. /// size: 31415
  445. /// }
  446. /// response_parameters:{
  447. /// size: 9
  448. /// }
  449. /// response_parameters:{
  450. /// size: 2653
  451. /// }
  452. /// response_parameters:{
  453. /// size: 58979
  454. /// }
  455. /// }
  456. /// ```
  457. ///
  458. /// Client asserts:
  459. /// - call was successful
  460. /// - exactly four responses
  461. /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979
  462. /// - clients are free to assert that the response payload body contents are zero and
  463. /// comparing the entire response messages against golden responses
  464. class ServerStreaming: InteroperabilityTest {
  465. func run(using connection: ClientConnection) throws {
  466. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  467. let responseSizes = [31415, 9, 2653, 58979]
  468. let request = Grpc_Testing_StreamingOutputCallRequest.with { request in
  469. request.responseParameters = responseSizes.map { .size($0) }
  470. }
  471. var payloads: [Grpc_Testing_Payload] = []
  472. let call = client.streamingOutputCall(request) { response in
  473. payloads.append(response.payload)
  474. }
  475. // Wait for the status first to ensure we've finished collecting responses.
  476. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  477. try assertEqual(payloads, responseSizes.map { .zeros(count: $0) })
  478. }
  479. }
  480. /// This test verifies that the server can compress streaming messages and disable compression on
  481. /// individual messages, expecting the server's response to be compressed or not according to the
  482. /// `response_compressed` boolean.
  483. ///
  484. /// Whether compression was actually performed is determined by the compression bit in the
  485. /// response's message flags. *Note that some languages may not have access to the message flags, in
  486. /// which case the client will be unable to verify that the `response_compressed` boolean is obeyed
  487. /// by the server*.
  488. ///
  489. /// Server features:
  490. /// - StreamingOutputCall
  491. /// - CompressedResponse
  492. ///
  493. /// Procedure:
  494. /// 1. Client calls StreamingOutputCall with `StreamingOutputCallRequest`:
  495. /// ```
  496. /// {
  497. /// response_parameters:{
  498. /// compressed: {
  499. /// value: true
  500. /// }
  501. /// size: 31415
  502. /// }
  503. /// response_parameters:{
  504. /// compressed: {
  505. /// value: false
  506. /// }
  507. /// size: 92653
  508. /// }
  509. /// }
  510. /// ```
  511. ///
  512. /// Client asserts:
  513. /// - call was successful
  514. /// - exactly two responses
  515. /// - if supported by the implementation, when `response_compressed` is false, the response's
  516. /// messages MUST NOT have the compressed message flag set.
  517. /// - if supported by the implementation, when `response_compressed` is true, the response's
  518. /// messages MUST have the compressed message flag set.
  519. /// - response payload bodies are sized (in order): 31415, 92653
  520. /// - clients are free to assert that the response payload body contents are zero and comparing the
  521. /// entire response messages against golden responses
  522. class ServerCompressedStreaming: InteroperabilityTest {
  523. func run(using connection: ClientConnection) throws {
  524. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  525. let request: Grpc_Testing_StreamingOutputCallRequest = .with { request in
  526. request.responseParameters = [
  527. .with {
  528. $0.compressed = true
  529. $0.size = 31415
  530. },
  531. .with {
  532. $0.compressed = false
  533. $0.size = 92653
  534. },
  535. ]
  536. }
  537. let options =
  538. CallOptions(
  539. messageEncoding: .enabled(
  540. .responsesOnly(
  541. decompressionLimit: .absolute(
  542. 1024 * 1024
  543. )
  544. )
  545. )
  546. )
  547. var payloads: [Grpc_Testing_Payload] = []
  548. let rpc = client.streamingOutputCall(request, callOptions: options) { response in
  549. payloads.append(response.payload)
  550. }
  551. // We can't verify that the compression bit was set, instead we verify that the encoding header
  552. // was sent by the server. This isn't quite the same since as it can still be set but the
  553. // compression may be not set.
  554. try waitAndAssert(rpc.initialMetadata) { headers in
  555. headers.first(name: "grpc-encoding") != nil
  556. }
  557. let responseSizes = [31415, 92653]
  558. // Wait for the status first to ensure we've finished collecting responses.
  559. try waitAndAssertEqual(rpc.status.map { $0.code }, .ok)
  560. try assertEqual(payloads, responseSizes.map { .zeros(count: $0) })
  561. }
  562. }
  563. /// This test verifies that full duplex bidi is supported.
  564. ///
  565. /// Server features:
  566. /// - FullDuplexCall
  567. ///
  568. /// Procedure:
  569. /// 1. Client calls FullDuplexCall with:
  570. /// ```
  571. /// {
  572. /// response_parameters:{
  573. /// size: 31415
  574. /// }
  575. /// payload:{
  576. /// body: 27182 bytes of zeros
  577. /// }
  578. /// }
  579. /// ```
  580. /// 2. After getting a reply, it sends:
  581. /// ```
  582. /// {
  583. /// response_parameters:{
  584. /// size: 9
  585. /// }
  586. /// payload:{
  587. /// body: 8 bytes of zeros
  588. /// }
  589. /// }
  590. /// ```
  591. /// 3. After getting a reply, it sends:
  592. /// ```
  593. /// {
  594. /// response_parameters:{
  595. /// size: 2653
  596. /// }
  597. /// payload:{
  598. /// body: 1828 bytes of zeros
  599. /// }
  600. /// }
  601. /// ```
  602. /// 4. After getting a reply, it sends:
  603. /// ```
  604. /// {
  605. /// response_parameters:{
  606. /// size: 58979
  607. /// }
  608. /// payload:{
  609. /// body: 45904 bytes of zeros
  610. /// }
  611. /// }
  612. /// ```
  613. /// 5. After getting a reply, client half-closes
  614. ///
  615. /// Client asserts:
  616. /// - call was successful
  617. /// - exactly four responses
  618. /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979
  619. /// - clients are free to assert that the response payload body contents are zero and
  620. /// comparing the entire response messages against golden responses
  621. class PingPong: InteroperabilityTest {
  622. func run(using connection: ClientConnection) throws {
  623. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  624. let requestSizes = [27182, 8, 1828, 45904]
  625. let responseSizes = [31415, 9, 2653, 58979]
  626. let responseReceived = DispatchSemaphore(value: 0)
  627. var payloads: [Grpc_Testing_Payload] = []
  628. let call = client.fullDuplexCall { response in
  629. payloads.append(response.payload)
  630. responseReceived.signal()
  631. }
  632. try zip(requestSizes, responseSizes).map { requestSize, responseSize in
  633. Grpc_Testing_StreamingOutputCallRequest.with { request in
  634. request.payload = .zeros(count: requestSize)
  635. request.responseParameters = [.size(responseSize)]
  636. }
  637. }.forEach { request in
  638. call.sendMessage(request, promise: nil)
  639. try assertEqual(responseReceived.wait(timeout: .now() + .seconds(1)), .success)
  640. }
  641. call.sendEnd(promise: nil)
  642. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  643. try assertEqual(payloads, responseSizes.map { .zeros(count: $0) })
  644. }
  645. }
  646. /// This test verifies that streams support having zero-messages in both directions.
  647. ///
  648. /// Server features:
  649. /// - FullDuplexCall
  650. ///
  651. /// Procedure:
  652. /// 1. Client calls FullDuplexCall and then half-closes
  653. ///
  654. /// Client asserts:
  655. /// - call was successful
  656. /// - exactly zero responses
  657. class EmptyStream: InteroperabilityTest {
  658. func run(using connection: ClientConnection) throws {
  659. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  660. var responses: [Grpc_Testing_StreamingOutputCallResponse] = []
  661. let call = client.fullDuplexCall { response in
  662. responses.append(response)
  663. }
  664. try call.sendEnd().wait()
  665. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  666. try assertEqual(responses, [])
  667. }
  668. }
  669. /// This test verifies that custom metadata in either binary or ascii format can be sent as
  670. /// initial-metadata by the client and as both initial- and trailing-metadata by the server.
  671. ///
  672. /// Server features:
  673. /// - UnaryCall
  674. /// - FullDuplexCall
  675. /// - Echo Metadata
  676. ///
  677. /// Procedure:
  678. /// 1. The client attaches custom metadata with the following keys and values
  679. /// to a UnaryCall with request:
  680. /// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
  681. /// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab
  682. /// ```
  683. /// {
  684. /// response_size: 314159
  685. /// payload:{
  686. /// body: 271828 bytes of zeros
  687. /// }
  688. /// }
  689. /// ```
  690. /// 2. The client attaches custom metadata with the following keys and values
  691. /// to a FullDuplexCall with request:
  692. /// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
  693. /// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab
  694. /// ```
  695. /// {
  696. /// response_parameters:{
  697. /// size: 314159
  698. /// }
  699. /// payload:{
  700. /// body: 271828 bytes of zeros
  701. /// }
  702. /// }
  703. /// ```
  704. /// and then half-closes
  705. ///
  706. /// Client asserts:
  707. /// - call was successful
  708. /// - metadata with key "x-grpc-test-echo-initial" and value "test_initial_metadata_value" is
  709. /// received in the initial metadata for calls in Procedure steps 1 and 2.
  710. /// - metadata with key "x-grpc-test-echo-trailing-bin" and value 0xababab is received in the
  711. /// trailing metadata for calls in Procedure steps 1 and 2.
  712. class CustomMetadata: InteroperabilityTest {
  713. let initialMetadataName = "x-grpc-test-echo-initial"
  714. let initialMetadataValue = "test_initial_metadata_value"
  715. let trailingMetadataName = "x-grpc-test-echo-trailing-bin"
  716. let trailingMetadataValue = Data([0xAB, 0xAB, 0xAB]).base64EncodedString()
  717. func checkMetadata<SpecificClientCall>(call: SpecificClientCall) throws
  718. where SpecificClientCall: ClientCall {
  719. let initialName = call.initialMetadata.map { $0[self.initialMetadataName] }
  720. try waitAndAssertEqual(initialName, [self.initialMetadataValue])
  721. let trailingName = call.trailingMetadata.map { $0[self.trailingMetadataName] }
  722. try waitAndAssertEqual(trailingName, [self.trailingMetadataValue])
  723. try waitAndAssertEqual(call.status.map { $0.code }, .ok)
  724. }
  725. func run(using connection: ClientConnection) throws {
  726. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  727. let unaryRequest = Grpc_Testing_SimpleRequest.with { request in
  728. request.responseSize = 314_159
  729. request.payload = .zeros(count: 217_828)
  730. }
  731. let customMetadata: HPACKHeaders = [
  732. self.initialMetadataName: self.initialMetadataValue,
  733. self.trailingMetadataName: self.trailingMetadataValue,
  734. ]
  735. let callOptions = CallOptions(customMetadata: customMetadata)
  736. let unaryCall = client.unaryCall(unaryRequest, callOptions: callOptions)
  737. try self.checkMetadata(call: unaryCall)
  738. let duplexCall = client.fullDuplexCall(callOptions: callOptions) { _ in }
  739. let duplexRequest = Grpc_Testing_StreamingOutputCallRequest.with { request in
  740. request.responseParameters = [.size(314_159)]
  741. request.payload = .zeros(count: 271_828)
  742. }
  743. duplexCall.sendMessage(duplexRequest, promise: nil)
  744. duplexCall.sendEnd(promise: nil)
  745. try self.checkMetadata(call: duplexCall)
  746. }
  747. }
  748. /// This test verifies unary calls succeed in sending messages, and propagate back status code and
  749. /// message sent along with the messages.
  750. ///
  751. /// Server features:
  752. /// - UnaryCall
  753. /// - FullDuplexCall
  754. /// - Echo Status
  755. ///
  756. /// Procedure:
  757. /// 1. Client calls UnaryCall with:
  758. /// ```
  759. /// {
  760. /// response_status:{
  761. /// code: 2
  762. /// message: "test status message"
  763. /// }
  764. /// }
  765. /// ```
  766. /// 2. Client calls FullDuplexCall with:
  767. /// ```
  768. /// {
  769. /// response_status:{
  770. /// code: 2
  771. /// message: "test status message"
  772. /// }
  773. /// }
  774. /// ```
  775. /// 3. and then half-closes
  776. ///
  777. /// Client asserts:
  778. /// - received status code is the same as the sent code for both Procedure steps 1 and 2
  779. /// - received status message is the same as the sent message for both Procedure steps 1 and 2
  780. class StatusCodeAndMessage: InteroperabilityTest {
  781. let expectedCode = 2
  782. let expectedMessage = "test status message"
  783. func checkStatus<SpecificClientCall>(call: SpecificClientCall) throws
  784. where SpecificClientCall: ClientCall {
  785. try waitAndAssertEqual(call.status.map { $0.code.rawValue }, self.expectedCode)
  786. try waitAndAssertEqual(call.status.map { $0.message }, self.expectedMessage)
  787. }
  788. func run(using connection: ClientConnection) throws {
  789. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  790. let echoStatus = Grpc_Testing_EchoStatus(
  791. code: Int32(self.expectedCode),
  792. message: self.expectedMessage
  793. )
  794. let unaryCall = client.unaryCall(.withStatus(of: echoStatus))
  795. try self.checkStatus(call: unaryCall)
  796. var responses: [Grpc_Testing_StreamingOutputCallResponse] = []
  797. let duplexCall = client.fullDuplexCall { response in
  798. responses.append(response)
  799. }
  800. duplexCall.sendMessage(.withStatus(of: echoStatus), promise: nil)
  801. duplexCall.sendEnd(promise: nil)
  802. try self.checkStatus(call: duplexCall)
  803. try assertEqual(responses, [])
  804. }
  805. }
  806. /// This test verifies Unicode and whitespace is correctly processed in status message. "\t" is
  807. /// horizontal tab. "\r" is carriage return. "\n" is line feed.
  808. ///
  809. /// Server features:
  810. /// - UnaryCall
  811. /// - Echo Status
  812. ///
  813. /// Procedure:
  814. /// 1. Client calls UnaryCall with:
  815. /// ```
  816. /// {
  817. /// response_status:{
  818. /// code: 2
  819. /// message: "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
  820. /// }
  821. /// }
  822. /// ```
  823. ///
  824. /// Client asserts:
  825. /// - received status code is the same as the sent code for Procedure step 1
  826. /// - received status message is the same as the sent message for Procedure step 1, including all
  827. /// whitespace characters
  828. class SpecialStatusMessage: InteroperabilityTest {
  829. func run(using connection: ClientConnection) throws {
  830. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  831. let code = 2
  832. let message = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
  833. let call = client.unaryCall(.withStatus(of: .init(code: Int32(code), message: message)))
  834. try waitAndAssertEqual(call.status.map { $0.code.rawValue }, code)
  835. try waitAndAssertEqual(call.status.map { $0.message }, message)
  836. }
  837. }
  838. /// This test verifies that calling an unimplemented RPC method returns the UNIMPLEMENTED status
  839. /// code.
  840. ///
  841. /// Server features: N/A
  842. ///
  843. /// Procedure:
  844. /// 1. Client calls grpc.testing.TestService/UnimplementedCall with an empty request (defined as
  845. /// grpc.testing.Empty):
  846. /// ```
  847. /// {
  848. /// }
  849. /// ```
  850. ///
  851. /// Client asserts:
  852. /// - received status code is 12 (UNIMPLEMENTED)
  853. class UnimplementedMethod: InteroperabilityTest {
  854. func run(using connection: ClientConnection) throws {
  855. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  856. let call = client.unimplementedCall(Grpc_Testing_Empty())
  857. try waitAndAssertEqual(call.status.map { $0.code }, .unimplemented)
  858. }
  859. }
  860. /// This test verifies calling an unimplemented server returns the UNIMPLEMENTED status code.
  861. ///
  862. /// Server features: N/A
  863. ///
  864. /// Procedure:
  865. /// 1. Client calls grpc.testing.UnimplementedService/UnimplementedCall with an empty request
  866. /// (defined as grpc.testing.Empty):
  867. /// ```
  868. /// {
  869. /// }
  870. /// ```
  871. ///
  872. /// Client asserts:
  873. /// - received status code is 12 (UNIMPLEMENTED)
  874. class UnimplementedService: InteroperabilityTest {
  875. func run(using connection: ClientConnection) throws {
  876. let client = Grpc_Testing_UnimplementedServiceNIOClient(channel: connection)
  877. let call = client.unimplementedCall(Grpc_Testing_Empty())
  878. try waitAndAssertEqual(call.status.map { $0.code }, .unimplemented)
  879. }
  880. }
  881. /// This test verifies that a request can be cancelled after metadata has been sent but before
  882. /// payloads are sent.
  883. ///
  884. /// Server features:
  885. /// - StreamingInputCall
  886. ///
  887. /// Procedure:
  888. /// 1. Client starts StreamingInputCall
  889. /// 2. Client immediately cancels request
  890. ///
  891. /// Client asserts:
  892. /// - Call completed with status CANCELLED
  893. class CancelAfterBegin: InteroperabilityTest {
  894. func run(using connection: ClientConnection) throws {
  895. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  896. let call = client.streamingInputCall()
  897. call.cancel(promise: nil)
  898. try waitAndAssertEqual(call.status.map { $0.code }, .cancelled)
  899. }
  900. }
  901. /// This test verifies that a request can be cancelled after receiving a message from the server.
  902. ///
  903. /// Server features:
  904. /// - FullDuplexCall
  905. ///
  906. /// Procedure:
  907. /// 1. Client starts FullDuplexCall with
  908. /// ```
  909. /// {
  910. /// response_parameters:{
  911. /// size: 31415
  912. /// }
  913. /// payload:{
  914. /// body: 27182 bytes of zeros
  915. /// }
  916. /// }
  917. /// ```
  918. /// 2. After receiving a response, client cancels request
  919. ///
  920. /// Client asserts:
  921. /// - Call completed with status CANCELLED
  922. class CancelAfterFirstResponse: InteroperabilityTest {
  923. func run(using connection: ClientConnection) throws {
  924. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  925. let promise = connection.eventLoop.makePromise(of: Void.self)
  926. let call = client.fullDuplexCall { _ in
  927. promise.succeed(())
  928. }
  929. promise.futureResult.whenSuccess {
  930. call.cancel(promise: nil)
  931. }
  932. let request = Grpc_Testing_StreamingOutputCallRequest.with { request in
  933. request.responseParameters = [.size(31415)]
  934. request.payload = .zeros(count: 27182)
  935. }
  936. call.sendMessage(request, promise: nil)
  937. try waitAndAssertEqual(call.status.map { $0.code }, .cancelled)
  938. }
  939. }
  940. /// This test verifies that an RPC request whose lifetime exceeds its configured timeout value
  941. /// will end with the DeadlineExceeded status.
  942. ///
  943. /// Server features:
  944. /// - FullDuplexCall
  945. ///
  946. /// Procedure:
  947. /// 1. Client calls FullDuplexCall with the following request and sets its timeout to 1ms
  948. /// ```
  949. /// {
  950. /// payload:{
  951. /// body: 27182 bytes of zeros
  952. /// }
  953. /// }
  954. /// ```
  955. /// 2. Client waits
  956. ///
  957. /// Client asserts:
  958. /// - Call completed with status DEADLINE_EXCEEDED.
  959. class TimeoutOnSleepingServer: InteroperabilityTest {
  960. func run(using connection: ClientConnection) throws {
  961. let client = Grpc_Testing_TestServiceNIOClient(channel: connection)
  962. let callOptions = CallOptions(timeLimit: .timeout(.milliseconds(1)))
  963. let call = client.fullDuplexCall(callOptions: callOptions) { _ in }
  964. try waitAndAssertEqual(call.status.map { $0.code }, .deadlineExceeded)
  965. }
  966. }