XCTestHelpers.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699
  1. /*
  2. * Copyright 2020, 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 NIOCore
  17. import NIOHPACK
  18. import NIOHTTP1
  19. import NIOHTTP2
  20. import XCTest
  21. @testable import GRPC
  22. struct UnwrapError: Error {}
  23. // We support Swift versions before 'XCTUnwrap' was introduced.
  24. func assertNotNil<Value>(
  25. _ expression: @autoclosure () throws -> Value?,
  26. message: @autoclosure () -> String = "Optional value was nil",
  27. file: StaticString = #filePath,
  28. line: UInt = #line
  29. ) throws -> Value {
  30. guard let value = try expression() else {
  31. XCTFail(message(), file: file, line: line)
  32. throw UnwrapError()
  33. }
  34. return value
  35. }
  36. func assertNoThrow<Value>(
  37. _ expression: @autoclosure () throws -> Value,
  38. message: @autoclosure () -> String = "Unexpected error thrown",
  39. file: StaticString = #filePath,
  40. line: UInt = #line
  41. ) throws -> Value {
  42. do {
  43. return try expression()
  44. } catch {
  45. XCTFail(message(), file: file, line: line)
  46. throw error
  47. }
  48. }
  49. // MARK: - Matchers.
  50. // The Swift 5.2 compiler will crash when trying to
  51. // inline this function if the tests are running in
  52. // release mode.
  53. @inline(never)
  54. func assertThat<Value>(
  55. _ expression: @autoclosure @escaping () throws -> Value,
  56. _ matcher: Matcher<Value>,
  57. file: StaticString = #filePath,
  58. line: UInt = #line
  59. ) {
  60. // For value matchers we'll assert that we don't throw by default.
  61. assertThat(try expression(), .doesNotThrow(matcher), file: file, line: line)
  62. }
  63. func assertThat<Value>(
  64. _ expression: @autoclosure @escaping () throws -> Value,
  65. _ matcher: ExpressionMatcher<Value>,
  66. file: StaticString = #filePath,
  67. line: UInt = #line
  68. ) {
  69. switch matcher.evaluate(expression) {
  70. case .match:
  71. ()
  72. case let .noMatch(actual: actual, expected: expected):
  73. XCTFail("ACTUAL: \(actual), EXPECTED: \(expected)", file: file, line: line)
  74. }
  75. }
  76. enum MatchResult {
  77. case match
  78. case noMatch(actual: String, expected: String)
  79. }
  80. struct Matcher<Value> {
  81. fileprivate typealias Evaluator = (Value) -> MatchResult
  82. private var matcher: Evaluator
  83. fileprivate init(_ matcher: @escaping Evaluator) {
  84. self.matcher = matcher
  85. }
  86. fileprivate func evaluate(_ value: Value) -> MatchResult {
  87. return self.matcher(value)
  88. }
  89. // MARK: Sugar
  90. /// Just returns the provided matcher.
  91. static func `is`<V>(_ matcher: Matcher<V>) -> Matcher<V> {
  92. return matcher
  93. }
  94. /// Just returns the provided matcher.
  95. static func and<V>(_ matcher: Matcher<V>) -> Matcher<V> {
  96. return matcher
  97. }
  98. // MARK: Equality
  99. /// Checks the equality of the actual value against the provided value. See `equalTo(_:)`.
  100. static func `is`<V: Equatable>(_ value: V) -> Matcher<V> {
  101. return .equalTo(value)
  102. }
  103. /// Checks the equality of the actual value against the provided value.
  104. static func equalTo<V: Equatable>(_ expected: V) -> Matcher<V> {
  105. return .init { actual in
  106. actual == expected
  107. ? .match
  108. : .noMatch(actual: "\(actual)", expected: "equal to \(expected)")
  109. }
  110. }
  111. /// Always returns a 'match', useful when the expected value is `Void`.
  112. static func isVoid() -> Matcher<Void> {
  113. return .init {
  114. return .match
  115. }
  116. }
  117. /// Matches if the value is `nil`.
  118. static func none<V>() -> Matcher<V?> {
  119. return .init { actual in
  120. actual == nil
  121. ? .match
  122. : .noMatch(actual: String(describing: actual), expected: "nil")
  123. }
  124. }
  125. /// Matches if the value is not `nil`.
  126. static func some<V>(_ matcher: Matcher<V>? = nil) -> Matcher<V?> {
  127. return .init { actual in
  128. if let actual = actual {
  129. return matcher?.evaluate(actual) ?? .match
  130. } else {
  131. return .noMatch(actual: "nil", expected: "not nil")
  132. }
  133. }
  134. }
  135. // MARK: Result
  136. static func success<V>(_ matcher: Matcher<V>? = nil) -> Matcher<Result<V, Error>> {
  137. return .init { actual in
  138. switch actual {
  139. case let .success(value):
  140. return matcher?.evaluate(value) ?? .match
  141. case let .failure(error):
  142. return .noMatch(actual: "\(error)", expected: "success")
  143. }
  144. }
  145. }
  146. static func success() -> Matcher<Result<Void, Error>> {
  147. return .init { actual in
  148. switch actual {
  149. case .success:
  150. return .match
  151. case let .failure(error):
  152. return .noMatch(actual: "\(error)", expected: "success")
  153. }
  154. }
  155. }
  156. static func failure<Success, Failure: Error>(
  157. _ matcher: Matcher<Failure>? = nil
  158. ) -> Matcher<Result<Success, Failure>> {
  159. return .init { actual in
  160. switch actual {
  161. case let .success(value):
  162. return .noMatch(actual: "\(value)", expected: "failure")
  163. case let .failure(error):
  164. return matcher?.evaluate(error) ?? .match
  165. }
  166. }
  167. }
  168. // MARK: Utility
  169. static func all<V>(_ matchers: Matcher<V>...) -> Matcher<V> {
  170. return .init { actual in
  171. for matcher in matchers {
  172. let result = matcher.evaluate(actual)
  173. switch result {
  174. case .noMatch:
  175. return result
  176. case .match:
  177. ()
  178. }
  179. }
  180. return .match
  181. }
  182. }
  183. // MARK: Type
  184. /// Checks that the actual value is an instance of the given type.
  185. static func instanceOf<V, Expected>(_: Expected.Type) -> Matcher<V> {
  186. return .init { actual in
  187. if actual is Expected {
  188. return .match
  189. } else {
  190. return .noMatch(
  191. actual: String(describing: type(of: actual)) + " (\(actual))",
  192. expected: "value of type \(Expected.self)"
  193. )
  194. }
  195. }
  196. }
  197. // MARK: Collection
  198. /// Checks whether the collection has the expected count.
  199. static func hasCount<C: Collection>(_ count: Int) -> Matcher<C> {
  200. return .init { actual in
  201. actual.count == count
  202. ? .match
  203. : .noMatch(actual: "has count \(actual.count)", expected: "count of \(count)")
  204. }
  205. }
  206. static func isEmpty<C: Collection>() -> Matcher<C> {
  207. return .init { actual in
  208. actual.isEmpty
  209. ? .match
  210. : .noMatch(actual: "has \(actual.count) items", expected: "is empty")
  211. }
  212. }
  213. // MARK: gRPC matchers
  214. static func hasCode(_ code: GRPCStatus.Code) -> Matcher<GRPCStatus> {
  215. return .init { actual in
  216. actual.code == code
  217. ? .match
  218. : .noMatch(actual: "has status code \(actual)", expected: "\(code)")
  219. }
  220. }
  221. static func metadata<Request>(
  222. _ matcher: Matcher<HPACKHeaders>? = nil
  223. ) -> Matcher<GRPCServerRequestPart<Request>> {
  224. return .init { actual in
  225. switch actual {
  226. case let .metadata(headers):
  227. return matcher?.evaluate(headers) ?? .match
  228. default:
  229. return .noMatch(actual: String(describing: actual), expected: "metadata")
  230. }
  231. }
  232. }
  233. static func message<Request>(
  234. _ matcher: Matcher<Request>? = nil
  235. ) -> Matcher<GRPCServerRequestPart<Request>> {
  236. return .init { actual in
  237. switch actual {
  238. case let .message(message):
  239. return matcher?.evaluate(message) ?? .match
  240. default:
  241. return .noMatch(actual: String(describing: actual), expected: "message")
  242. }
  243. }
  244. }
  245. static func metadata<Response>(
  246. _ matcher: Matcher<HPACKHeaders>? = nil
  247. ) -> Matcher<GRPCServerResponsePart<Response>> {
  248. return .init { actual in
  249. switch actual {
  250. case let .metadata(headers):
  251. return matcher?.evaluate(headers) ?? .match
  252. default:
  253. return .noMatch(actual: String(describing: actual), expected: "metadata")
  254. }
  255. }
  256. }
  257. static func message<Response>(
  258. _ matcher: Matcher<Response>? = nil
  259. ) -> Matcher<GRPCServerResponsePart<Response>> {
  260. return .init { actual in
  261. switch actual {
  262. case let .message(message, _):
  263. return matcher?.evaluate(message) ?? .match
  264. default:
  265. return .noMatch(actual: String(describing: actual), expected: "message")
  266. }
  267. }
  268. }
  269. static func end<Response>(
  270. status statusMatcher: Matcher<GRPCStatus>? = nil,
  271. trailers trailersMatcher: Matcher<HPACKHeaders>? = nil
  272. ) -> Matcher<GRPCServerResponsePart<Response>> {
  273. return .init { actual in
  274. switch actual {
  275. case let .end(status, trailers):
  276. let statusMatch = (statusMatcher?.evaluate(status) ?? .match)
  277. switch statusMatcher?.evaluate(status) ?? .match {
  278. case .match:
  279. return trailersMatcher?.evaluate(trailers) ?? .match
  280. case .noMatch:
  281. return statusMatch
  282. }
  283. default:
  284. return .noMatch(actual: String(describing: actual), expected: "end")
  285. }
  286. }
  287. }
  288. static func sendTrailers(
  289. _ matcher: Matcher<HPACKHeaders>? = nil
  290. ) -> Matcher<HTTP2ToRawGRPCStateMachine.SendEndAction> {
  291. return .init { actual in
  292. switch actual {
  293. case let .sendTrailers(trailers):
  294. return matcher?.evaluate(trailers) ?? .match
  295. case .sendTrailersAndFinish:
  296. return .noMatch(actual: "sendTrailersAndFinish", expected: "sendTrailers")
  297. case let .failure(error):
  298. return .noMatch(actual: "\(error)", expected: "sendTrailers")
  299. }
  300. }
  301. }
  302. static func sendTrailersAndFinish(
  303. _ matcher: Matcher<HPACKHeaders>? = nil
  304. ) -> Matcher<HTTP2ToRawGRPCStateMachine.SendEndAction> {
  305. return .init { actual in
  306. switch actual {
  307. case let .sendTrailersAndFinish(trailers):
  308. return matcher?.evaluate(trailers) ?? .match
  309. case .sendTrailers:
  310. return .noMatch(actual: "sendTrailers", expected: "sendTrailersAndFinish")
  311. case let .failure(error):
  312. return .noMatch(actual: "\(error)", expected: "sendTrailersAndFinish")
  313. }
  314. }
  315. }
  316. static func failure(
  317. _ matcher: Matcher<Error>? = nil
  318. ) -> Matcher<HTTP2ToRawGRPCStateMachine.SendEndAction> {
  319. return .init { actual in
  320. switch actual {
  321. case .sendTrailers:
  322. return .noMatch(actual: "sendTrailers", expected: "failure")
  323. case .sendTrailersAndFinish:
  324. return .noMatch(actual: "sendTrailersAndFinish", expected: "failure")
  325. case let .failure(error):
  326. return matcher?.evaluate(error) ?? .match
  327. }
  328. }
  329. }
  330. // MARK: HTTP/1
  331. static func head(
  332. status: HTTPResponseStatus,
  333. headers: HTTPHeaders? = nil
  334. ) -> Matcher<HTTPServerResponsePart> {
  335. return .init { actual in
  336. switch actual {
  337. case let .head(head):
  338. let statusMatches = Matcher.is(status).evaluate(head.status)
  339. switch statusMatches {
  340. case .match:
  341. return headers.map { Matcher.is($0).evaluate(head.headers) } ?? .match
  342. case .noMatch:
  343. return statusMatches
  344. }
  345. case .body, .end:
  346. return .noMatch(actual: "\(actual)", expected: "head")
  347. }
  348. }
  349. }
  350. static func body(_ matcher: Matcher<ByteBuffer>? = nil) -> Matcher<HTTPServerResponsePart> {
  351. return .init { actual in
  352. switch actual {
  353. case let .body(.byteBuffer(buffer)):
  354. return matcher.map { $0.evaluate(buffer) } ?? .match
  355. default:
  356. return .noMatch(actual: "\(actual)", expected: "body")
  357. }
  358. }
  359. }
  360. static func end() -> Matcher<HTTPServerResponsePart> {
  361. return .init { actual in
  362. switch actual {
  363. case .end:
  364. return .match
  365. default:
  366. return .noMatch(actual: "\(actual)", expected: "end")
  367. }
  368. }
  369. }
  370. // MARK: HTTP/2
  371. static func contains(
  372. _ name: String,
  373. _ values: [String]? = nil
  374. ) -> Matcher<HPACKHeaders> {
  375. return .init { actual in
  376. let headers = actual[canonicalForm: name]
  377. if headers.isEmpty {
  378. return .noMatch(actual: "does not contain '\(name)'", expected: "contains '\(name)'")
  379. } else {
  380. return values.map { Matcher.equalTo($0).evaluate(headers) } ?? .match
  381. }
  382. }
  383. }
  384. static func contains(
  385. caseSensitive caseSensitiveName: String
  386. ) -> Matcher<HPACKHeaders> {
  387. return .init { actual in
  388. for (name, _, _) in actual {
  389. if name == caseSensitiveName {
  390. return .match
  391. }
  392. }
  393. return .noMatch(
  394. actual: "does not contain '\(caseSensitiveName)'",
  395. expected: "contains '\(caseSensitiveName)'"
  396. )
  397. }
  398. }
  399. static func headers(
  400. _ headers: Matcher<HPACKHeaders>? = nil,
  401. endStream: Bool? = nil
  402. ) -> Matcher<HTTP2Frame.FramePayload> {
  403. return .init { actual in
  404. switch actual {
  405. case let .headers(payload):
  406. let headersMatch = headers?.evaluate(payload.headers)
  407. switch headersMatch {
  408. case .none,
  409. .some(.match):
  410. return endStream.map { Matcher.is($0).evaluate(payload.endStream) } ?? .match
  411. case .some(.noMatch):
  412. return headersMatch!
  413. }
  414. default:
  415. return .noMatch(actual: "\(actual)", expected: "headers")
  416. }
  417. }
  418. }
  419. static func data(
  420. buffer: ByteBuffer? = nil,
  421. endStream: Bool? = nil
  422. ) -> Matcher<HTTP2Frame.FramePayload> {
  423. return .init { actual in
  424. switch actual {
  425. case let .data(payload):
  426. let endStreamMatches = endStream.map { Matcher.is($0).evaluate(payload.endStream) }
  427. switch (endStreamMatches, payload.data) {
  428. case let (.none, .byteBuffer(b)),
  429. let (.some(.match), .byteBuffer(b)):
  430. return buffer.map { Matcher.is($0).evaluate(b) } ?? .match
  431. case (.some(.noMatch), .byteBuffer):
  432. return endStreamMatches!
  433. case (_, .fileRegion):
  434. preconditionFailure("Unexpected IOData.fileRegion")
  435. }
  436. default:
  437. return .noMatch(actual: "\(actual)", expected: "data")
  438. }
  439. }
  440. }
  441. static func trailersOnly(
  442. code: GRPCStatus.Code,
  443. contentType: String = "application/grpc"
  444. ) -> Matcher<HPACKHeaders> {
  445. return .all(
  446. .contains(":status", ["200"]),
  447. .contains("content-type", [contentType]),
  448. .contains("grpc-status", ["\(code.rawValue)"])
  449. )
  450. }
  451. static func trailers(code: GRPCStatus.Code, message: String) -> Matcher<HPACKHeaders> {
  452. return .all(
  453. .contains("grpc-status", ["\(code.rawValue)"]),
  454. .contains("grpc-message", [message])
  455. )
  456. }
  457. // MARK: HTTP2ToRawGRPCStateMachine.Action
  458. static func errorCaught() -> Matcher<HTTP2ToRawGRPCStateMachine.ReadNextMessageAction> {
  459. return .init { actual in
  460. switch actual {
  461. case .errorCaught:
  462. return .match
  463. default:
  464. return .noMatch(actual: "\(actual)", expected: "errorCaught")
  465. }
  466. }
  467. }
  468. static func configure() -> Matcher<HTTP2ToRawGRPCStateMachine.ReceiveHeadersAction> {
  469. return .init { actual in
  470. switch actual {
  471. case .configure:
  472. return .match
  473. default:
  474. return .noMatch(actual: "\(actual)", expected: "configurePipeline")
  475. }
  476. }
  477. }
  478. static func rejectRPC(
  479. _ matcher: Matcher<HPACKHeaders>? = nil
  480. ) -> Matcher<HTTP2ToRawGRPCStateMachine.ReceiveHeadersAction> {
  481. return .init { actual in
  482. switch actual {
  483. case let .rejectRPC(headers):
  484. return matcher?.evaluate(headers) ?? .match
  485. default:
  486. return .noMatch(actual: "\(actual)", expected: "rejectRPC")
  487. }
  488. }
  489. }
  490. static func forwardHeaders() -> Matcher<HTTP2ToRawGRPCStateMachine.PipelineConfiguredAction> {
  491. return .init { actual in
  492. switch actual {
  493. case .forwardHeaders:
  494. return .match
  495. default:
  496. return .noMatch(actual: "\(actual)", expected: "forwardHeaders")
  497. }
  498. }
  499. }
  500. static func none() -> Matcher<HTTP2ToRawGRPCStateMachine.ReadNextMessageAction> {
  501. return .init { actual in
  502. switch actual {
  503. case .none:
  504. return .match
  505. default:
  506. return .noMatch(actual: "\(actual)", expected: "none")
  507. }
  508. }
  509. }
  510. static func forwardMessage() -> Matcher<HTTP2ToRawGRPCStateMachine.ReadNextMessageAction> {
  511. return .init { actual in
  512. switch actual {
  513. case .forwardMessage:
  514. return .match
  515. default:
  516. return .noMatch(actual: "\(actual)", expected: "forwardMessage")
  517. }
  518. }
  519. }
  520. static func forwardEnd() -> Matcher<HTTP2ToRawGRPCStateMachine.ReadNextMessageAction> {
  521. return .init { actual in
  522. switch actual {
  523. case .forwardEnd:
  524. return .match
  525. default:
  526. return .noMatch(actual: "\(actual)", expected: "forwardEnd")
  527. }
  528. }
  529. }
  530. static func forwardHeadersThenRead()
  531. -> Matcher<HTTP2ToRawGRPCStateMachine.PipelineConfiguredAction>
  532. {
  533. return .init { actual in
  534. switch actual {
  535. case .forwardHeadersAndRead:
  536. return .match
  537. default:
  538. return .noMatch(actual: "\(actual)", expected: "forwardHeadersAndRead")
  539. }
  540. }
  541. }
  542. static func forwardMessageThenRead()
  543. -> Matcher<HTTP2ToRawGRPCStateMachine.ReadNextMessageAction>
  544. {
  545. return .init { actual in
  546. switch actual {
  547. case .forwardMessageThenReadNextMessage:
  548. return .match
  549. default:
  550. return .noMatch(actual: "\(actual)", expected: "forwardMessageThenReadNextMessage")
  551. }
  552. }
  553. }
  554. }
  555. struct ExpressionMatcher<Value> {
  556. typealias Expression = () throws -> Value
  557. private typealias Evaluator = (Expression) -> MatchResult
  558. private var evaluator: Evaluator
  559. private init(_ evaluator: @escaping Evaluator) {
  560. self.evaluator = evaluator
  561. }
  562. fileprivate func evaluate(_ expression: Expression) -> MatchResult {
  563. return self.evaluator(expression)
  564. }
  565. /// Asserts that the expression does not throw and error. Returns the result of any provided
  566. /// matcher on the result of the expression.
  567. static func doesNotThrow<V>(_ matcher: Matcher<V>? = nil) -> ExpressionMatcher<V> {
  568. return .init { expression in
  569. do {
  570. let value = try expression()
  571. return matcher?.evaluate(value) ?? .match
  572. } catch {
  573. return .noMatch(actual: "threw '\(error)'", expected: "should not throw error")
  574. }
  575. }
  576. }
  577. /// Asserts that the expression throws and error. Returns the result of any provided matcher
  578. /// on the error thrown by the expression.
  579. static func `throws`<V>(_ matcher: Matcher<Error>? = nil) -> ExpressionMatcher<V> {
  580. return .init { expression in
  581. do {
  582. let value = try expression()
  583. return .noMatch(actual: "returned '\(value)'", expected: "should throw error")
  584. } catch {
  585. return matcher?.evaluate(error) ?? .match
  586. }
  587. }
  588. }
  589. }
  590. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
  591. func assertThat<Value>(
  592. _ expression: @autoclosure @escaping () async throws -> Value,
  593. _ matcher: Matcher<Value>,
  594. file: StaticString = #filePath,
  595. line: UInt = #line
  596. ) async {
  597. // For value matchers we'll assert that we don't throw by default.
  598. await assertThat(try await expression(), .doesNotThrow(matcher), file: file, line: line)
  599. }
  600. @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *)
  601. func assertThat<Value>(
  602. _ expression: @autoclosure @escaping () async throws -> Value,
  603. _ matcher: ExpressionMatcher<Value>,
  604. file: StaticString = #filePath,
  605. line: UInt = #line
  606. ) async {
  607. // Create a shim here from async-await world...
  608. let result: Result<Value, Error>
  609. do {
  610. let value = try await expression()
  611. result = .success(value)
  612. } catch {
  613. result = .failure(error)
  614. }
  615. switch matcher.evaluate(result.get) {
  616. case .match:
  617. ()
  618. case let .noMatch(actual: actual, expected: expected):
  619. XCTFail("ACTUAL: \(actual), EXPECTED: \(expected)", file: file, line: line)
  620. }
  621. }