InteroperabilityTestCases.swift 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996
  1. /*
  2. * Copyright 2024, 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. internal import GRPCCore
  17. private import struct Foundation.Data
  18. /// This test verifies that implementations support zero-size messages. Ideally, client
  19. /// implementations would verify that the request and response were zero bytes serialized, but
  20. /// this is generally prohibitive to perform, so is not required.
  21. ///
  22. /// Server features:
  23. /// - EmptyCall
  24. ///
  25. /// Procedure:
  26. /// 1. Client calls EmptyCall with the default Empty message
  27. ///
  28. /// Client asserts:
  29. /// - call was successful
  30. /// - response is non-null
  31. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  32. struct EmptyUnary: InteroperabilityTest {
  33. func run(client: GRPCClient) async throws {
  34. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  35. try await testServiceClient.emptyCall(
  36. request: ClientRequest.Single(message: Grpc_Testing_Empty())
  37. ) { response in
  38. try assertEqual(response.message, Grpc_Testing_Empty())
  39. }
  40. }
  41. }
  42. /// This test verifies unary calls succeed in sending messages, and touches on flow control (even
  43. /// if compression is enabled on the channel).
  44. ///
  45. /// Server features:
  46. /// - UnaryCall
  47. ///
  48. /// Procedure:
  49. /// 1. Client calls UnaryCall with:
  50. /// ```
  51. /// {
  52. /// response_size: 314159
  53. /// payload:{
  54. /// body: 271828 bytes of zeros
  55. /// }
  56. /// }
  57. /// ```
  58. ///
  59. /// Client asserts:
  60. /// - call was successful
  61. /// - response payload body is 314159 bytes in size
  62. /// - clients are free to assert that the response payload body contents are zero and comparing
  63. /// the entire response message against a golden response
  64. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  65. struct LargeUnary: InteroperabilityTest {
  66. func run(client: GRPCClient) async throws {
  67. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  68. let request = Grpc_Testing_SimpleRequest.with { request in
  69. request.responseSize = 314_159
  70. request.payload = Grpc_Testing_Payload.with {
  71. $0.body = Data(count: 271_828)
  72. }
  73. }
  74. try await testServiceClient.unaryCall(
  75. request: ClientRequest.Single(message: request)
  76. ) { response in
  77. try assertEqual(
  78. response.message.payload,
  79. Grpc_Testing_Payload.with {
  80. $0.body = Data(count: 314_159)
  81. }
  82. )
  83. }
  84. }
  85. }
  86. /// This test verifies the client can compress unary messages by sending two unary calls, for
  87. /// compressed and uncompressed payloads. It also sends an initial probing request to verify
  88. /// whether the server supports the CompressedRequest feature by checking if the probing call
  89. /// fails with an `INVALID_ARGUMENT` status.
  90. ///
  91. /// Server features:
  92. /// - UnaryCall
  93. /// - CompressedRequest
  94. ///
  95. /// Procedure:
  96. /// 1. Client calls UnaryCall with the feature probe, an *uncompressed* message:
  97. /// ```
  98. /// {
  99. /// expect_compressed:{
  100. /// value: true
  101. /// }
  102. /// response_size: 314159
  103. /// payload:{
  104. /// body: 271828 bytes of zeros
  105. /// }
  106. /// }
  107. /// ```
  108. /// 2. Client calls UnaryCall with the *compressed* message:
  109. /// ```
  110. /// {
  111. /// expect_compressed:{
  112. /// value: true
  113. /// }
  114. /// response_size: 314159
  115. /// payload:{
  116. /// body: 271828 bytes of zeros
  117. /// }
  118. /// }
  119. /// ```
  120. /// 3. Client calls UnaryCall with the *uncompressed* message:
  121. /// ```
  122. /// {
  123. /// expect_compressed:{
  124. /// value: false
  125. /// }
  126. /// response_size: 314159
  127. /// payload:{
  128. /// body: 271828 bytes of zeros
  129. /// }
  130. /// }
  131. /// ```
  132. ///
  133. /// Client asserts:
  134. /// - First call failed with `INVALID_ARGUMENT` status.
  135. /// - Subsequent calls were successful.
  136. /// - Response payload body is 314159 bytes in size.
  137. /// - Clients are free to assert that the response payload body contents are zeros and comparing the
  138. /// entire response message against a golden response.
  139. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  140. class ClientCompressedUnary: InteroperabilityTest {
  141. func run(client: GRPCClient) async throws {
  142. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  143. let compressedRequest = Grpc_Testing_SimpleRequest.with { request in
  144. request.expectCompressed = .with { $0.value = true }
  145. request.responseSize = 314_159
  146. request.payload = .with { $0.body = Data(repeating: 0, count: 271_828) }
  147. }
  148. var uncompressedRequest = compressedRequest
  149. uncompressedRequest.expectCompressed = .with { $0.value = false }
  150. // For unary RPCs we disable compression at the call level.
  151. var options = CallOptions.defaults
  152. // With compression expected but *disabled*.
  153. options.compression = CompressionAlgorithm.none
  154. try await testServiceClient.unaryCall(
  155. request: ClientRequest.Single(message: compressedRequest),
  156. options: options
  157. ) { response in
  158. switch response.accepted {
  159. case .success:
  160. throw AssertionFailure(message: "The result should be an error.")
  161. case .failure(let error):
  162. try assertEqual(error.code, .invalidArgument)
  163. }
  164. }
  165. // With compression expected and enabled.
  166. options.compression = .gzip
  167. try await testServiceClient.unaryCall(
  168. request: ClientRequest.Single(message: compressedRequest),
  169. options: options
  170. ) { response in
  171. switch response.accepted {
  172. case .success(let success):
  173. try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159))
  174. case .failure:
  175. throw AssertionFailure(message: "Response should have been accepted.")
  176. }
  177. }
  178. // With compression not expected and disabled.
  179. options.compression = CompressionAlgorithm.none
  180. try await testServiceClient.unaryCall(
  181. request: ClientRequest.Single(message: uncompressedRequest),
  182. options: options
  183. ) { response in
  184. switch response.accepted {
  185. case .success(let success):
  186. try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159))
  187. case .failure:
  188. throw AssertionFailure(message: "Response should have been accepted.")
  189. }
  190. }
  191. }
  192. }
  193. /// This test verifies the server can compress unary messages. It sends two unary
  194. /// requests, expecting the server's response to be compressed or not according to
  195. /// the `response_compressed` boolean.
  196. ///
  197. /// Whether compression was actually performed is determined by the compression bit
  198. /// in the response's message flags. *Note that some languages may not have access
  199. /// to the message flags, in which case the client will be unable to verify that
  200. /// the `response_compressed` boolean is obeyed by the server*.
  201. ///
  202. ///
  203. /// Server features:
  204. /// - UnaryCall
  205. /// - CompressedResponse
  206. ///
  207. /// Procedure:
  208. /// 1. Client calls UnaryCall with `SimpleRequest`:
  209. /// ```
  210. /// {
  211. /// response_compressed:{
  212. /// value: true
  213. /// }
  214. /// response_size: 314159
  215. /// payload:{
  216. /// body: 271828 bytes of zeros
  217. /// }
  218. /// }
  219. /// ```
  220. /// ```
  221. /// {
  222. /// response_compressed:{
  223. /// value: false
  224. /// }
  225. /// response_size: 314159
  226. /// payload:{
  227. /// body: 271828 bytes of zeros
  228. /// }
  229. /// }
  230. /// ```
  231. ///
  232. /// Client asserts:
  233. /// - call was successful
  234. /// - if supported by the implementation, when `response_compressed` is true, the response MUST have
  235. /// the compressed message flag set.
  236. /// - if supported by the implementation, when `response_compressed` is false, the response MUST NOT
  237. /// have the compressed message flag set.
  238. /// - response payload body is 314159 bytes in size in both cases.
  239. /// - clients are free to assert that the response payload body contents are zero and comparing the
  240. /// entire response message against a golden response
  241. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  242. class ServerCompressedUnary: InteroperabilityTest {
  243. func run(client: GRPCClient) async throws {
  244. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  245. let compressedRequest = Grpc_Testing_SimpleRequest.with { request in
  246. request.responseCompressed = .with { $0.value = true }
  247. request.responseSize = 314_159
  248. request.payload = .with { $0.body = Data(repeating: 0, count: 271_828) }
  249. }
  250. try await testServiceClient.unaryCall(
  251. request: ClientRequest.Single(message: compressedRequest)
  252. ) { response in
  253. // We can't verify that the compression bit was set, instead we verify that the encoding header
  254. // was sent by the server. This isn't quite the same since as it can still be set but the
  255. // compression may _not_ be set.
  256. try assertTrue(response.metadata["grpc-encoding"].contains { $0 != "identity" })
  257. switch response.accepted {
  258. case .success(let success):
  259. try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159))
  260. case .failure:
  261. throw AssertionFailure(message: "Response should have been accepted.")
  262. }
  263. }
  264. var uncompressedRequest = compressedRequest
  265. uncompressedRequest.responseCompressed.value = false
  266. try await testServiceClient.unaryCall(
  267. request: ClientRequest.Single(message: compressedRequest)
  268. ) { response in
  269. // We can't even check for the 'grpc-encoding' header here since it could be set with the
  270. // compression bit on the message not set.
  271. switch response.accepted {
  272. case .success(let success):
  273. try assertEqual(success.message.get().payload.body, Data(repeating: 0, count: 314_159))
  274. case .failure:
  275. throw AssertionFailure(
  276. message: "Response should have been accepted."
  277. )
  278. }
  279. }
  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. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  327. struct ClientStreaming: InteroperabilityTest {
  328. func run(client: GRPCClient) async throws {
  329. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  330. let request = ClientRequest.Stream { writer in
  331. for bytes in [27182, 8, 1828, 45904] {
  332. let message = Grpc_Testing_StreamingInputCallRequest.with {
  333. $0.payload = Grpc_Testing_Payload.with {
  334. $0.body = Data(count: bytes)
  335. }
  336. }
  337. try await writer.write(message)
  338. }
  339. }
  340. try await testServiceClient.streamingInputCall(request: request) { response in
  341. try assertEqual(response.message.aggregatedPayloadSize, 74922)
  342. }
  343. }
  344. }
  345. /// This test verifies that server-only streaming succeeds.
  346. ///
  347. /// Server features:
  348. /// - StreamingOutputCall
  349. ///
  350. /// Procedure:
  351. /// 1. Client calls StreamingOutputCall with StreamingOutputCallRequest:
  352. /// ```
  353. /// {
  354. /// response_parameters:{
  355. /// size: 31415
  356. /// }
  357. /// response_parameters:{
  358. /// size: 9
  359. /// }
  360. /// response_parameters:{
  361. /// size: 2653
  362. /// }
  363. /// response_parameters:{
  364. /// size: 58979
  365. /// }
  366. /// }
  367. /// ```
  368. ///
  369. /// Client asserts:
  370. /// - call was successful
  371. /// - exactly four responses
  372. /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979
  373. /// - clients are free to assert that the response payload body contents are zero and
  374. /// comparing the entire response messages against golden responses
  375. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  376. struct ServerStreaming: InteroperabilityTest {
  377. func run(client: GRPCClient) async throws {
  378. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  379. let responseSizes = [31415, 9, 2653, 58979]
  380. let request = Grpc_Testing_StreamingOutputCallRequest.with { request in
  381. request.responseParameters = responseSizes.map {
  382. var parameter = Grpc_Testing_ResponseParameters()
  383. parameter.size = Int32($0)
  384. return parameter
  385. }
  386. }
  387. try await testServiceClient.streamingOutputCall(
  388. request: ClientRequest.Single(message: request)
  389. ) { response in
  390. var responseParts = response.messages.makeAsyncIterator()
  391. // There are 4 response sizes, so if there isn't a message for each one,
  392. // it means that the client didn't receive 4 messages back.
  393. for responseSize in responseSizes {
  394. if let message = try await responseParts.next() {
  395. try assertEqual(message.payload.body.count, responseSize)
  396. } else {
  397. throw AssertionFailure(
  398. message: "There were less than four responses received."
  399. )
  400. }
  401. }
  402. // Check that there were not more than 4 responses from the server.
  403. try assertEqual(try await responseParts.next(), nil)
  404. }
  405. }
  406. }
  407. /// This test verifies that the server can compress streaming messages and disable compression on
  408. /// individual messages, expecting the server's response to be compressed or not according to the
  409. /// `response_compressed` boolean.
  410. ///
  411. /// Whether compression was actually performed is determined by the compression bit in the
  412. /// response's message flags. *Note that some languages may not have access to the message flags, in
  413. /// which case the client will be unable to verify that the `response_compressed` boolean is obeyed
  414. /// by the server*.
  415. ///
  416. /// Server features:
  417. /// - StreamingOutputCall
  418. /// - CompressedResponse
  419. ///
  420. /// Procedure:
  421. /// 1. Client calls StreamingOutputCall with `StreamingOutputCallRequest`:
  422. /// ```
  423. /// {
  424. /// response_parameters:{
  425. /// compressed: {
  426. /// value: true
  427. /// }
  428. /// size: 31415
  429. /// }
  430. /// response_parameters:{
  431. /// compressed: {
  432. /// value: false
  433. /// }
  434. /// size: 92653
  435. /// }
  436. /// }
  437. /// ```
  438. ///
  439. /// Client asserts:
  440. /// - call was successful
  441. /// - exactly two responses
  442. /// - if supported by the implementation, when `response_compressed` is false, the response's
  443. /// messages MUST NOT have the compressed message flag set.
  444. /// - if supported by the implementation, when `response_compressed` is true, the response's
  445. /// messages MUST have the compressed message flag set.
  446. /// - response payload bodies are sized (in order): 31415, 92653
  447. /// - clients are free to assert that the response payload body contents are zero and comparing the
  448. /// entire response messages against golden responses
  449. class ServerCompressedStreaming: InteroperabilityTest {
  450. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  451. func run(client: GRPCClient) async throws {
  452. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  453. let request: Grpc_Testing_StreamingOutputCallRequest = .with { request in
  454. request.responseParameters = [
  455. .with {
  456. $0.compressed = .with { $0.value = true }
  457. $0.size = 31415
  458. },
  459. .with {
  460. $0.compressed = .with { $0.value = false }
  461. $0.size = 92653
  462. },
  463. ]
  464. }
  465. let responseSizes = [31415, 92653]
  466. try await testServiceClient.streamingOutputCall(
  467. request: ClientRequest.Single(message: request)
  468. ) { response in
  469. var payloads = [Grpc_Testing_Payload]()
  470. switch response.accepted {
  471. case .success(let success):
  472. // We can't verify that the compression bit was set, instead we verify that the encoding header
  473. // was sent by the server. This isn't quite the same since as it can still be set but the
  474. // compression may be not set.
  475. try assertTrue(success.metadata["grpc-encoding"].contains { $0 != "identity" })
  476. for try await part in success.bodyParts {
  477. switch part {
  478. case .message(let message):
  479. payloads.append(message.payload)
  480. case .trailingMetadata:
  481. ()
  482. }
  483. }
  484. case .failure:
  485. throw AssertionFailure(message: "Response should have been accepted.")
  486. }
  487. try assertEqual(
  488. payloads,
  489. responseSizes.map { size in
  490. Grpc_Testing_Payload.with {
  491. $0.body = Data(repeating: 0, count: size)
  492. }
  493. }
  494. )
  495. }
  496. }
  497. }
  498. /// This test verifies that full duplex bidi is supported.
  499. ///
  500. /// Server features:
  501. /// - FullDuplexCall
  502. ///
  503. /// Procedure:
  504. /// 1. Client calls FullDuplexCall with:
  505. /// ```
  506. /// {
  507. /// response_parameters:{
  508. /// size: 31415
  509. /// }
  510. /// payload:{
  511. /// body: 27182 bytes of zeros
  512. /// }
  513. /// }
  514. /// ```
  515. /// 2. After getting a reply, it sends:
  516. /// ```
  517. /// {
  518. /// response_parameters:{
  519. /// size: 9
  520. /// }
  521. /// payload:{
  522. /// body: 8 bytes of zeros
  523. /// }
  524. /// }
  525. /// ```
  526. /// 3. After getting a reply, it sends:
  527. /// ```
  528. /// {
  529. /// response_parameters:{
  530. /// size: 2653
  531. /// }
  532. /// payload:{
  533. /// body: 1828 bytes of zeros
  534. /// }
  535. /// }
  536. /// ```
  537. /// 4. After getting a reply, it sends:
  538. /// ```
  539. /// {
  540. /// response_parameters:{
  541. /// size: 58979
  542. /// }
  543. /// payload:{
  544. /// body: 45904 bytes of zeros
  545. /// }
  546. /// }
  547. /// ```
  548. /// 5. After getting a reply, client half-closes
  549. ///
  550. /// Client asserts:
  551. /// - call was successful
  552. /// - exactly four responses
  553. /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979
  554. /// - clients are free to assert that the response payload body contents are zero and
  555. /// comparing the entire response messages against golden responses
  556. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  557. struct PingPong: InteroperabilityTest {
  558. func run(client: GRPCClient) async throws {
  559. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  560. let ids = AsyncStream.makeStream(of: Int.self)
  561. let request = ClientRequest.Stream { writer in
  562. let sizes = [(31_415, 27_182), (9, 8), (2_653, 1_828), (58_979, 45_904)]
  563. for try await id in ids.stream {
  564. var message = Grpc_Testing_StreamingOutputCallRequest()
  565. switch id {
  566. case 1 ... 4:
  567. let (responseSize, bodySize) = sizes[id - 1]
  568. message.responseParameters = [
  569. Grpc_Testing_ResponseParameters.with {
  570. $0.size = Int32(responseSize)
  571. }
  572. ]
  573. message.payload = Grpc_Testing_Payload.with {
  574. $0.body = Data(count: bodySize)
  575. }
  576. default:
  577. // When the id is higher than 4 it means the client received all the expected responses
  578. // and it doesn't need to send another message.
  579. return
  580. }
  581. try await writer.write(message)
  582. }
  583. }
  584. ids.continuation.yield(1)
  585. try await testServiceClient.fullDuplexCall(request: request) { response in
  586. var id = 1
  587. for try await message in response.messages {
  588. switch id {
  589. case 1:
  590. try assertEqual(message.payload.body, Data(count: 31_415))
  591. case 2:
  592. try assertEqual(message.payload.body, Data(count: 9))
  593. case 3:
  594. try assertEqual(message.payload.body, Data(count: 2_653))
  595. case 4:
  596. try assertEqual(message.payload.body, Data(count: 58_979))
  597. default:
  598. throw AssertionFailure(
  599. message: "We should only receive messages with ids between 1 and 4."
  600. )
  601. }
  602. // Add the next id to the continuation.
  603. id += 1
  604. ids.continuation.yield(id)
  605. }
  606. }
  607. }
  608. }
  609. /// This test verifies that streams support having zero-messages in both directions.
  610. ///
  611. /// Server features:
  612. /// - FullDuplexCall
  613. ///
  614. /// Procedure:
  615. /// 1. Client calls FullDuplexCall and then half-closes
  616. ///
  617. /// Client asserts:
  618. /// - call was successful
  619. /// - exactly zero responses
  620. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  621. struct EmptyStream: InteroperabilityTest {
  622. func run(client: GRPCClient) async throws {
  623. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  624. let request = ClientRequest.Stream<Grpc_Testing_StreamingOutputCallRequest> { _ in }
  625. try await testServiceClient.fullDuplexCall(request: request) { response in
  626. var messages = response.messages.makeAsyncIterator()
  627. try await assertEqual(messages.next(), nil)
  628. }
  629. }
  630. }
  631. /// This test verifies that custom metadata in either binary or ascii format can be sent as
  632. /// initial-metadata by the client and as both initial- and trailing-metadata by the server.
  633. ///
  634. /// Server features:
  635. /// - UnaryCall
  636. /// - FullDuplexCall
  637. /// - Echo Metadata
  638. ///
  639. /// Procedure:
  640. /// 1. The client attaches custom metadata with the following keys and values
  641. /// to a UnaryCall with request:
  642. /// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
  643. /// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab
  644. /// ```
  645. /// {
  646. /// response_size: 314159
  647. /// payload:{
  648. /// body: 271828 bytes of zeros
  649. /// }
  650. /// }
  651. /// ```
  652. /// 2. The client attaches custom metadata with the following keys and values
  653. /// to a FullDuplexCall with request:
  654. /// - key: "x-grpc-test-echo-initial", value: "test_initial_metadata_value"
  655. /// - key: "x-grpc-test-echo-trailing-bin", value: 0xababab
  656. /// ```
  657. /// {
  658. /// response_parameters:{
  659. /// size: 314159
  660. /// }
  661. /// payload:{
  662. /// body: 271828 bytes of zeros
  663. /// }
  664. /// }
  665. /// ```
  666. /// and then half-closes
  667. ///
  668. /// Client asserts:
  669. /// - call was successful
  670. /// - metadata with key "x-grpc-test-echo-initial" and value "test_initial_metadata_value" is
  671. /// received in the initial metadata for calls in Procedure steps 1 and 2.
  672. /// - metadata with key "x-grpc-test-echo-trailing-bin" and value 0xababab is received in the
  673. /// trailing metadata for calls in Procedure steps 1 and 2.
  674. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  675. struct CustomMetadata: InteroperabilityTest {
  676. let initialMetadataName = "x-grpc-test-echo-initial"
  677. let initialMetadataValue = "test_initial_metadata_value"
  678. let trailingMetadataName = "x-grpc-test-echo-trailing-bin"
  679. let trailingMetadataValue: [UInt8] = [0xAB, 0xAB, 0xAB]
  680. func checkInitialMetadata(_ metadata: Metadata) throws {
  681. let values = metadata[self.initialMetadataName]
  682. try assertEqual(Array(values), [.string(self.initialMetadataValue)])
  683. }
  684. func checkTrailingMetadata(_ metadata: Metadata) throws {
  685. let values = metadata[self.trailingMetadataName]
  686. try assertEqual(Array(values), [.binary(self.trailingMetadataValue)])
  687. }
  688. func run(client: GRPCClient) async throws {
  689. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  690. let unaryRequest = Grpc_Testing_SimpleRequest.with { request in
  691. request.responseSize = 314_159
  692. request.payload = Grpc_Testing_Payload.with {
  693. $0.body = Data(count: 271_828)
  694. }
  695. }
  696. let metadata: Metadata = [
  697. self.initialMetadataName: .string(self.initialMetadataValue),
  698. self.trailingMetadataName: .binary(self.trailingMetadataValue),
  699. ]
  700. try await testServiceClient.unaryCall(
  701. request: ClientRequest.Single(message: unaryRequest, metadata: metadata)
  702. ) { response in
  703. // Check the initial metadata.
  704. let receivedInitialMetadata = response.metadata
  705. try checkInitialMetadata(receivedInitialMetadata)
  706. // Check the message.
  707. try assertEqual(response.message.payload.body, Data(count: 314_159))
  708. // Check the trailing metadata.
  709. try checkTrailingMetadata(response.trailingMetadata)
  710. }
  711. let streamingRequest = ClientRequest.Stream(metadata: metadata) { writer in
  712. let message = Grpc_Testing_StreamingOutputCallRequest.with {
  713. $0.responseParameters = [
  714. Grpc_Testing_ResponseParameters.with {
  715. $0.size = 314_159
  716. }
  717. ]
  718. $0.payload = Grpc_Testing_Payload.with {
  719. $0.body = Data(count: 271_828)
  720. }
  721. }
  722. try await writer.write(message)
  723. }
  724. try await testServiceClient.fullDuplexCall(request: streamingRequest) { response in
  725. switch response.accepted {
  726. case .success(let contents):
  727. // Check the initial metadata.
  728. let receivedInitialMetadata = response.metadata
  729. try self.checkInitialMetadata(receivedInitialMetadata)
  730. let parts = try await contents.bodyParts.reduce(into: []) { $0.append($1) }
  731. try assertEqual(parts.count, 2)
  732. for part in parts {
  733. switch part {
  734. // Check the message.
  735. case .message(let message):
  736. try assertEqual(message.payload.body, Data(count: 314_159))
  737. // Check the trailing metadata.
  738. case .trailingMetadata(let receivedTrailingMetadata):
  739. try self.checkTrailingMetadata(receivedTrailingMetadata)
  740. }
  741. }
  742. case .failure:
  743. throw AssertionFailure(
  744. message: "The client should have received a response from the server."
  745. )
  746. }
  747. }
  748. }
  749. }
  750. /// This test verifies unary calls succeed in sending messages, and propagate back status code and
  751. /// message sent along with the messages.
  752. ///
  753. /// Server features:
  754. /// - UnaryCall
  755. /// - FullDuplexCall
  756. /// - Echo Status
  757. ///
  758. /// Procedure:
  759. /// 1. Client calls UnaryCall with:
  760. /// ```
  761. /// {
  762. /// response_status:{
  763. /// code: 2
  764. /// message: "test status message"
  765. /// }
  766. /// }
  767. /// ```
  768. /// 2. Client calls FullDuplexCall with:
  769. /// ```
  770. /// {
  771. /// response_status:{
  772. /// code: 2
  773. /// message: "test status message"
  774. /// }
  775. /// }
  776. /// ```
  777. /// 3. and then half-closes
  778. ///
  779. /// Client asserts:
  780. /// - received status code is the same as the sent code for both Procedure steps 1 and 2
  781. /// - received status message is the same as the sent message for both Procedure steps 1 and 2
  782. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  783. struct StatusCodeAndMessage: InteroperabilityTest {
  784. let expectedCode = 2
  785. let expectedMessage = "test status message"
  786. func run(client: GRPCClient) async throws {
  787. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  788. let message = Grpc_Testing_SimpleRequest.with {
  789. $0.responseStatus = Grpc_Testing_EchoStatus.with {
  790. $0.code = Int32(self.expectedCode)
  791. $0.message = self.expectedMessage
  792. }
  793. }
  794. try await testServiceClient.unaryCall(
  795. request: ClientRequest.Single(message: message)
  796. ) { response in
  797. switch response.accepted {
  798. case .failure(let error):
  799. try assertEqual(error.code.rawValue, self.expectedCode)
  800. try assertEqual(error.message, self.expectedMessage)
  801. case .success:
  802. throw AssertionFailure(
  803. message:
  804. "The client should receive an error with the status code and message sent by the client."
  805. )
  806. }
  807. }
  808. let request = ClientRequest.Stream { writer in
  809. let message = Grpc_Testing_StreamingOutputCallRequest.with {
  810. $0.responseStatus = Grpc_Testing_EchoStatus.with {
  811. $0.code = Int32(self.expectedCode)
  812. $0.message = self.expectedMessage
  813. }
  814. }
  815. try await writer.write(message)
  816. }
  817. try await testServiceClient.fullDuplexCall(request: request) { response in
  818. do {
  819. for try await _ in response.messages {
  820. throw AssertionFailure(
  821. message:
  822. "The client should receive an error with the status code and message sent by the client."
  823. )
  824. }
  825. } catch let error as RPCError {
  826. try assertEqual(error.code.rawValue, self.expectedCode)
  827. try assertEqual(error.message, self.expectedMessage)
  828. }
  829. }
  830. }
  831. }
  832. /// This test verifies Unicode and whitespace is correctly processed in status message. "\t" is
  833. /// horizontal tab. "\r" is carriage return. "\n" is line feed.
  834. ///
  835. /// Server features:
  836. /// - UnaryCall
  837. /// - Echo Status
  838. ///
  839. /// Procedure:
  840. /// 1. Client calls UnaryCall with:
  841. /// ```
  842. /// {
  843. /// response_status:{
  844. /// code: 2
  845. /// message: "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
  846. /// }
  847. /// }
  848. /// ```
  849. ///
  850. /// Client asserts:
  851. /// - received status code is the same as the sent code for Procedure step 1
  852. /// - received status message is the same as the sent message for Procedure step 1, including all
  853. /// whitespace characters
  854. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  855. struct SpecialStatusMessage: InteroperabilityTest {
  856. func run(client: GRPCClient) async throws {
  857. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  858. let responseMessage = "\t\ntest with whitespace\r\nand Unicode BMP ☺ and non-BMP 😈\t\n"
  859. let message = Grpc_Testing_SimpleRequest.with {
  860. $0.responseStatus = Grpc_Testing_EchoStatus.with {
  861. $0.code = 2
  862. $0.message = responseMessage
  863. }
  864. }
  865. try await testServiceClient.unaryCall(
  866. request: ClientRequest.Single(message: message)
  867. ) { response in
  868. switch response.accepted {
  869. case .success:
  870. throw AssertionFailure(
  871. message: "The response should be an error with the error code 2."
  872. )
  873. case .failure(let error):
  874. try assertEqual(error.code.rawValue, 2)
  875. try assertEqual(error.message, responseMessage)
  876. }
  877. }
  878. }
  879. }
  880. /// This test verifies that calling an unimplemented RPC method returns the UNIMPLEMENTED status
  881. /// code.
  882. ///
  883. /// Server features: N/A
  884. ///
  885. /// Procedure:
  886. /// 1. Client calls grpc.testing.TestService/UnimplementedCall with an empty request (defined as
  887. /// grpc.testing.Empty):
  888. /// ```
  889. /// {
  890. /// }
  891. /// ```
  892. ///
  893. /// Client asserts:
  894. /// - received status code is 12 (UNIMPLEMENTED)
  895. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  896. struct UnimplementedMethod: InteroperabilityTest {
  897. func run(client: GRPCClient) async throws {
  898. let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client)
  899. try await testServiceClient.unimplementedCall(
  900. request: ClientRequest.Single(message: Grpc_Testing_Empty())
  901. ) { response in
  902. switch response.accepted {
  903. case .success:
  904. throw AssertionFailure(
  905. message: "The result should be an error."
  906. )
  907. case .failure(let error):
  908. try assertEqual(error.code, .unimplemented)
  909. }
  910. }
  911. }
  912. }
  913. /// This test verifies calling an unimplemented server returns the UNIMPLEMENTED status code.
  914. ///
  915. /// Server features: N/A
  916. ///
  917. /// Procedure:
  918. /// 1. Client calls grpc.testing.UnimplementedService/UnimplementedCall with an empty request
  919. /// (defined as grpc.testing.Empty):
  920. /// ```
  921. /// {
  922. /// }
  923. /// ```
  924. ///
  925. /// Client asserts:
  926. /// - received status code is 12 (UNIMPLEMENTED)
  927. @available(macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0, *)
  928. struct UnimplementedService: InteroperabilityTest {
  929. func run(client: GRPCClient) async throws {
  930. let unimplementedServiceClient = Grpc_Testing_UnimplementedService.Client(wrapping: client)
  931. try await unimplementedServiceClient.unimplementedCall(
  932. request: ClientRequest.Single(message: Grpc_Testing_Empty())
  933. ) { response in
  934. switch response.accepted {
  935. case .success:
  936. throw AssertionFailure(message: "The result should be an error.")
  937. case .failure(let error):
  938. try assertEqual(error.code, .unimplemented)
  939. }
  940. }
  941. }
  942. }