GRPCTests.swift 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469
  1. /*
  2. * Copyright 2017, 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 Foundation
  18. @testable import SwiftGRPC
  19. import XCTest
  20. class gRPCTests: XCTestCase {
  21. // We have seen this test flake out in rare cases fairly often due to race conditions.
  22. // To detect such rare errors, we run the tests several times.
  23. // (By now, all known errors should have been fixed, but we'd still like to detect new ones.)
  24. let testRepetitions = 10
  25. func testConnectivity() {
  26. for _ in 0..<testRepetitions {
  27. runTest(useSSL: false)
  28. }
  29. }
  30. func testConnectivitySecure() {
  31. for _ in 0..<testRepetitions {
  32. runTest(useSSL: true)
  33. }
  34. }
  35. }
  36. let address = "localhost:8085"
  37. let host = "example.com"
  38. let evenClientText = "hello, server!"
  39. let oddClientText = "hello, server, please fail!"
  40. let serverText = "hello, client!"
  41. let initialClientMetadata =
  42. [
  43. "x": "xylophone",
  44. "y": "yu",
  45. "z": "zither"
  46. ]
  47. let initialServerMetadata =
  48. [
  49. "a": "Apple",
  50. "b": "Banana",
  51. "c": "Cherry"
  52. ]
  53. let trailingServerMetadata =
  54. [
  55. // We have more than ten entries here to ensure that even large metadata entries work
  56. // and aren't limited by e.g. a fixed-size entry buffer.
  57. "0": "zero",
  58. "1": "one",
  59. "2": "two",
  60. "3": "three",
  61. "4": "four",
  62. "5": "five",
  63. "6": "six",
  64. "7": "seven",
  65. "8": "eight",
  66. "9": "nine",
  67. "10": "ten",
  68. "11": "eleven",
  69. "12": "twelve"
  70. ]
  71. let steps = 100
  72. let hello = "/hello.unary"
  73. let helloServerStream = "/hello.server-stream"
  74. let helloBiDiStream = "/hello.bidi-stream"
  75. // Return code/message for unary test
  76. let oddStatusMessage = "OK"
  77. let evenStatusMessage = "some other status message"
  78. // Parsing very large messages as String is very inefficient,
  79. // so we avoid it anything above this threshold.
  80. let sizeThresholdForReturningDataVerbatim = 10_000
  81. func runTest(useSSL: Bool) {
  82. gRPC.initialize()
  83. var serverRunningSemaphore: DispatchSemaphore?
  84. // create the server
  85. let server: Server
  86. if useSSL {
  87. server = Server(address: address,
  88. key: String(data: serverKey, encoding: .utf8)!,
  89. certs: String(data: serverCertificate, encoding: .utf8)!)
  90. } else {
  91. server = Server(address: address)
  92. }
  93. // start the server
  94. do {
  95. serverRunningSemaphore = try runServer(server: server)
  96. } catch {
  97. XCTFail("server error \(error)")
  98. }
  99. // run the client
  100. do {
  101. try runClient(useSSL: useSSL)
  102. } catch {
  103. XCTFail("client error \(error)")
  104. }
  105. // stop the server
  106. server.stop()
  107. // wait until the server has shut down
  108. _ = serverRunningSemaphore!.wait()
  109. }
  110. func verify_metadata(_ metadata: Metadata, expected: [String: String], file: StaticString = #file, line: UInt = #line) {
  111. XCTAssertGreaterThanOrEqual(metadata.count(), expected.count)
  112. var allPresentKeys = Set<String>()
  113. for i in 0..<metadata.count() {
  114. guard let expectedValue = expected[metadata.key(i)!]
  115. else { continue }
  116. allPresentKeys.insert(metadata.key(i)!)
  117. XCTAssertEqual(metadata.value(i), expectedValue, file: file, line: line)
  118. }
  119. XCTAssertEqual(allPresentKeys.sorted(), expected.keys.sorted(), file: file, line: line)
  120. }
  121. func runClient(useSSL: Bool) throws {
  122. let channel: Channel
  123. if useSSL {
  124. channel = Channel(address: address,
  125. certificates: String(data: trustCollectionCertificate, encoding: .utf8)!,
  126. arguments: [.sslTargetNameOverride(host)])
  127. } else {
  128. channel = Channel(address: address, secure: false)
  129. }
  130. channel.host = host
  131. let largeMessage = Data(repeating: 88 /* 'X' */, count: 4_000_000)
  132. for _ in 0..<10 {
  133. // Send several calls to each server we spin up, to ensure that each individual server can handle many requests.
  134. try callUnary(channel: channel)
  135. try callServerStream(channel: channel)
  136. try callBiDiStream(channel: channel)
  137. }
  138. // Test sending a large message.
  139. try callUnaryIndividual(channel: channel, message: largeMessage, shouldSucceed: true)
  140. try callUnaryIndividual(channel: channel, message: largeMessage, shouldSucceed: true)
  141. }
  142. func callUnary(channel: Channel) throws {
  143. let evenMessage = evenClientText.data(using: .utf8)!
  144. let oddMessage = oddClientText.data(using: .utf8)!
  145. for i in 0..<steps {
  146. try callUnaryIndividual(channel: channel,
  147. message: (i % 2) == 0 ? evenMessage : oddMessage,
  148. shouldSucceed: (i % 2) == 0)
  149. }
  150. }
  151. func callUnaryIndividual(channel: Channel, message: Data, shouldSucceed: Bool) throws {
  152. let sem = DispatchSemaphore(value: 0)
  153. let method = hello
  154. let call = try channel.makeCall(method)
  155. let metadata = try Metadata(initialClientMetadata)
  156. try call.start(.unary, metadata: metadata, message: message) {
  157. response in
  158. // verify the basic response from the server
  159. XCTAssertEqual(response.statusCode, .ok)
  160. XCTAssertEqual(response.statusMessage, shouldSucceed ? evenStatusMessage : oddStatusMessage)
  161. //print("response.resultData?.count", response.resultData?.count)
  162. // verify the message from the server
  163. if shouldSucceed {
  164. if let resultData = response.resultData {
  165. if resultData.count >= sizeThresholdForReturningDataVerbatim {
  166. XCTAssertEqual(message, resultData)
  167. } else {
  168. let messageString = String(data: resultData, encoding: .utf8)
  169. XCTAssertEqual(messageString, serverText)
  170. }
  171. } else {
  172. XCTFail("callUnary response missing")
  173. }
  174. }
  175. // verify the initial metadata from the server
  176. if let initialMetadata = response.initialMetadata {
  177. verify_metadata(initialMetadata, expected: initialServerMetadata)
  178. } else {
  179. XCTFail("callUnary initial metadata missing")
  180. }
  181. // verify the trailing metadata from the server
  182. if let trailingMetadata = response.trailingMetadata {
  183. verify_metadata(trailingMetadata, expected: trailingServerMetadata)
  184. } else {
  185. XCTFail("callUnary trailing metadata missing")
  186. }
  187. // report completion
  188. sem.signal()
  189. }
  190. // wait for the call to complete
  191. _ = sem.wait()
  192. }
  193. func callServerStream(channel: Channel) throws {
  194. let message = evenClientText.data(using: .utf8)
  195. let metadata = try Metadata(initialClientMetadata)
  196. let sem = DispatchSemaphore(value: 0)
  197. let method = helloServerStream
  198. let call = try channel.makeCall(method)
  199. try call.start(.serverStreaming, metadata: metadata, message: message) {
  200. response in
  201. XCTAssertEqual(response.statusCode, .ok)
  202. XCTAssertEqual(response.statusMessage, "Custom Status Message ServerStreaming")
  203. // verify the trailing metadata from the server
  204. if let trailingMetadata = response.trailingMetadata {
  205. verify_metadata(trailingMetadata, expected: trailingServerMetadata)
  206. } else {
  207. XCTFail("callServerStream trailing metadata missing")
  208. }
  209. sem.signal() // signal call is finished
  210. }
  211. for _ in 0..<steps {
  212. let messageSem = DispatchSemaphore(value: 0)
  213. try call.receiveMessage { callResult in
  214. if let data = callResult.resultData {
  215. let messageString = String(data: data, encoding: .utf8)
  216. XCTAssertEqual(messageString, serverText)
  217. } else {
  218. XCTFail("callServerStream unexpected result: \(callResult)")
  219. }
  220. messageSem.signal()
  221. }
  222. _ = messageSem.wait()
  223. }
  224. _ = sem.wait()
  225. }
  226. let clientPing = "ping"
  227. let serverPong = "pong"
  228. func callBiDiStream(channel: Channel) throws {
  229. let metadata = try Metadata(initialClientMetadata)
  230. let sem = DispatchSemaphore(value: 0)
  231. let method = helloBiDiStream
  232. let call = try channel.makeCall(method)
  233. try call.start(.bidiStreaming, metadata: metadata, message: nil) {
  234. response in
  235. XCTAssertEqual(response.statusCode, .ok)
  236. XCTAssertEqual(response.statusMessage, "Custom Status Message BiDi")
  237. // verify the trailing metadata from the server
  238. if let trailingMetadata = response.trailingMetadata {
  239. verify_metadata(trailingMetadata, expected: trailingServerMetadata)
  240. } else {
  241. XCTFail("callBiDiStream trailing metadata missing")
  242. }
  243. sem.signal() // signal call is finished
  244. }
  245. // Send pings
  246. let message = clientPing.data(using: .utf8)!
  247. for _ in 0..<steps {
  248. try call.sendMessage(data: message) { err in
  249. XCTAssertNil(err)
  250. }
  251. call.messageQueueEmpty.wait()
  252. }
  253. let closeSem = DispatchSemaphore(value: 0)
  254. try call.close {
  255. closeSem.signal()
  256. }
  257. _ = closeSem.wait()
  258. // Receive pongs
  259. for _ in 0..<steps {
  260. let pongSem = DispatchSemaphore(value: 0)
  261. try call.receiveMessage { callResult in
  262. if let data = callResult.resultData {
  263. let messageString = String(data: data, encoding: .utf8)
  264. XCTAssertEqual(messageString, serverPong)
  265. } else {
  266. XCTFail("callBiDiStream unexpected result: \(callResult)")
  267. }
  268. pongSem.signal()
  269. }
  270. _ = pongSem.wait()
  271. }
  272. _ = sem.wait()
  273. }
  274. func runServer(server: Server) throws -> DispatchSemaphore {
  275. let sem = DispatchSemaphore(value: 0)
  276. server.run { requestHandler in
  277. do {
  278. if let method = requestHandler.method {
  279. switch method {
  280. case hello:
  281. try handleUnary(requestHandler: requestHandler)
  282. case helloServerStream:
  283. try handleServerStream(requestHandler: requestHandler)
  284. case helloBiDiStream:
  285. try handleBiDiStream(requestHandler: requestHandler)
  286. default:
  287. XCTFail("Invalid method \(method)")
  288. }
  289. }
  290. } catch {
  291. XCTFail("error \(error)")
  292. }
  293. }
  294. server.onCompletion = {
  295. // return from runServer()
  296. sem.signal()
  297. }
  298. // wait for the server to exit
  299. return sem
  300. }
  301. func handleUnary(requestHandler: Handler) throws {
  302. XCTAssertEqual(requestHandler.host, host)
  303. XCTAssertEqual(requestHandler.method, hello)
  304. let initialMetadata = requestHandler.requestMetadata
  305. verify_metadata(initialMetadata, expected: initialClientMetadata)
  306. let initialMetadataToSend = try Metadata(initialServerMetadata)
  307. let receiveSem = DispatchSemaphore(value: 0)
  308. var inputMessage: Data?
  309. try requestHandler.receiveMessage(initialMetadata: initialMetadataToSend) {
  310. if let messageData = $0 {
  311. inputMessage = messageData
  312. if messageData.count < sizeThresholdForReturningDataVerbatim {
  313. let messageString = String(data: messageData, encoding: .utf8)!
  314. XCTAssertTrue(messageString == evenClientText || messageString == oddClientText,
  315. "handleUnary unexpected message string \(messageString)")
  316. }
  317. } else {
  318. XCTFail("handleUnary message missing")
  319. }
  320. receiveSem.signal()
  321. }
  322. receiveSem.wait()
  323. // We need to return status OK in both cases, as it seems like the server might never send out the last few messages
  324. // once it has been asked to send a non-OK status. Alternatively, we could send a non-OK status here, but then we
  325. // would need to sleep for a few milliseconds before sending the non-OK status.
  326. let replyMessage = (inputMessage == nil || inputMessage!.count < sizeThresholdForReturningDataVerbatim)
  327. ? serverText.data(using: .utf8)!
  328. : inputMessage!
  329. let trailingMetadataToSend = try Metadata(trailingServerMetadata)
  330. if let inputMessage = inputMessage,
  331. inputMessage.count >= sizeThresholdForReturningDataVerbatim
  332. || inputMessage == evenClientText.data(using: .utf8)! {
  333. try requestHandler.sendResponse(message: replyMessage,
  334. status: ServerStatus(code: .ok,
  335. message: evenStatusMessage,
  336. trailingMetadata: trailingMetadataToSend))
  337. } else {
  338. try requestHandler.sendStatus(ServerStatus(code: .ok,
  339. message: oddStatusMessage,
  340. trailingMetadata: trailingMetadataToSend))
  341. }
  342. }
  343. func handleServerStream(requestHandler: Handler) throws {
  344. XCTAssertEqual(requestHandler.host, host)
  345. XCTAssertEqual(requestHandler.method, helloServerStream)
  346. let initialMetadata = requestHandler.requestMetadata
  347. verify_metadata(initialMetadata, expected: initialClientMetadata)
  348. let initialMetadataToSend = try Metadata(initialServerMetadata)
  349. try requestHandler.receiveMessage(initialMetadata: initialMetadataToSend) {
  350. if let messageData = $0 {
  351. let messageString = String(data: messageData, encoding: .utf8)
  352. XCTAssertEqual(messageString, evenClientText)
  353. } else {
  354. XCTFail("handleServerStream message missing")
  355. }
  356. }
  357. let replyMessage = serverText.data(using: .utf8)!
  358. for _ in 0..<steps {
  359. try requestHandler.call.sendMessage(data: replyMessage) { error in
  360. XCTAssertNil(error)
  361. }
  362. requestHandler.call.messageQueueEmpty.wait()
  363. }
  364. let trailingMetadataToSend = try Metadata(trailingServerMetadata)
  365. try requestHandler.sendStatus(ServerStatus(
  366. // We need to return status OK here, as it seems like the server might never send out the last few messages once it
  367. // has been asked to send a non-OK status. Alternatively, we could send a non-OK status here, but then we would need
  368. // to sleep for a few milliseconds before sending the non-OK status.
  369. code: .ok,
  370. message: "Custom Status Message ServerStreaming",
  371. trailingMetadata: trailingMetadataToSend))
  372. }
  373. func handleBiDiStream(requestHandler: Handler) throws {
  374. XCTAssertEqual(requestHandler.host, host)
  375. XCTAssertEqual(requestHandler.method, helloBiDiStream)
  376. let initialMetadata = requestHandler.requestMetadata
  377. verify_metadata(initialMetadata, expected: initialClientMetadata)
  378. let initialMetadataToSend = try Metadata(initialServerMetadata)
  379. let sendMetadataSem = DispatchSemaphore(value: 0)
  380. try requestHandler.sendMetadata(initialMetadata: initialMetadataToSend) { _ in
  381. _ = sendMetadataSem.signal()
  382. }
  383. _ = sendMetadataSem.wait()
  384. // Receive remaining pings
  385. for _ in 0..<steps {
  386. let receiveSem = DispatchSemaphore(value: 0)
  387. try requestHandler.call.receiveMessage { callStatus in
  388. if let messageData = callStatus.resultData {
  389. let messageString = String(data: messageData, encoding: .utf8)
  390. XCTAssertEqual(messageString, clientPing)
  391. } else {
  392. XCTFail("handleBiDiStream message empty")
  393. }
  394. receiveSem.signal()
  395. }
  396. _ = receiveSem.wait()
  397. }
  398. // Send back pongs
  399. let replyMessage = serverPong.data(using: .utf8)!
  400. for _ in 0..<steps {
  401. try requestHandler.call.sendMessage(data: replyMessage) { error in
  402. XCTAssertNil(error)
  403. }
  404. requestHandler.call.messageQueueEmpty.wait()
  405. }
  406. let trailingMetadataToSend = try Metadata(trailingServerMetadata)
  407. let sem = DispatchSemaphore(value: 0)
  408. try requestHandler.sendStatus(ServerStatus(
  409. // We need to return status OK here, as it seems like the server might never send out the last few messages once it
  410. // has been asked to send a non-OK status. Alternatively, we could send a non-OK status here, but then we would need
  411. // to sleep for a few milliseconds before sending the non-OK status.
  412. code: .ok,
  413. message: "Custom Status Message BiDi",
  414. trailingMetadata: trailingMetadataToSend)) { sem.signal() }
  415. _ = sem.wait()
  416. }