GRPCTests.swift 14 KB

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