XCTestHelpers.swift 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360
  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. @testable import GRPC
  17. import NIOHPACK
  18. import XCTest
  19. struct UnwrapError: Error {}
  20. // We support Swift versions before 'XCTUnwrap' was introduced.
  21. func assertNotNil<Value>(
  22. _ expression: @autoclosure () throws -> Value?,
  23. message: @autoclosure () -> String = "Optional value was nil",
  24. file: StaticString = #file,
  25. line: UInt = #line
  26. ) throws -> Value {
  27. guard let value = try expression() else {
  28. XCTFail(message(), file: file, line: line)
  29. throw UnwrapError()
  30. }
  31. return value
  32. }
  33. func assertNoThrow<Value>(
  34. _ expression: @autoclosure () throws -> Value,
  35. message: @autoclosure () -> String = "Unexpected error thrown",
  36. file: StaticString = #file,
  37. line: UInt = #line
  38. ) throws -> Value {
  39. do {
  40. return try expression()
  41. } catch {
  42. XCTFail(message(), file: file, line: line)
  43. throw error
  44. }
  45. }
  46. // MARK: - Matchers.
  47. func assertThat<Value>(
  48. _ expression: @autoclosure @escaping () throws -> Value,
  49. _ matcher: Matcher<Value>,
  50. file: StaticString = #file,
  51. line: UInt = #line
  52. ) {
  53. // For value matchers we'll assert that we don't throw by default.
  54. assertThat(try expression(), .doesNotThrow(matcher), file: file, line: line)
  55. }
  56. func assertThat<Value>(
  57. _ expression: @autoclosure @escaping () throws -> Value,
  58. _ matcher: ExpressionMatcher<Value>,
  59. file: StaticString = #file,
  60. line: UInt = #line
  61. ) {
  62. switch matcher.evaluate(expression) {
  63. case .match:
  64. ()
  65. case let .noMatch(actual: actual, expected: expected):
  66. XCTFail("ACTUAL: \(actual), EXPECTED: \(expected)", file: file, line: line)
  67. }
  68. }
  69. enum MatchResult {
  70. case match
  71. case noMatch(actual: String, expected: String)
  72. }
  73. struct Matcher<Value> {
  74. private typealias Evaluator = (Value) -> MatchResult
  75. private var matcher: Evaluator
  76. private init(_ matcher: @escaping Evaluator) {
  77. self.matcher = matcher
  78. }
  79. fileprivate func evaluate(_ value: Value) -> MatchResult {
  80. return self.matcher(value)
  81. }
  82. // MARK: Sugar
  83. /// Just returns the provided matcher.
  84. static func `is`<Value>(_ matcher: Matcher<Value>) -> Matcher<Value> {
  85. return matcher
  86. }
  87. /// Just returns the provided matcher.
  88. static func and<Value>(_ matcher: Matcher<Value>) -> Matcher<Value> {
  89. return matcher
  90. }
  91. // MARK: Equality
  92. /// Checks the equality of the actual value against the provided value. See `equalTo(_:)`.
  93. static func `is`<Value: Equatable>(_ value: Value) -> Matcher<Value> {
  94. return .equalTo(value)
  95. }
  96. /// Checks the equality of the actual value against the provided value.
  97. static func equalTo<Value: Equatable>(_ expected: Value) -> Matcher<Value> {
  98. return .init { actual in
  99. actual == expected
  100. ? .match
  101. : .noMatch(actual: "\(actual)", expected: "equal to \(expected)")
  102. }
  103. }
  104. /// Always returns a 'match', useful when the expected value is `Void`.
  105. static func isVoid() -> Matcher<Void> {
  106. return .init {
  107. return .match
  108. }
  109. }
  110. /// Matches if the value is `nil`.
  111. static func `nil`<Value>() -> Matcher<Value?> {
  112. return .init { actual in
  113. actual == nil
  114. ? .match
  115. : .noMatch(actual: String(describing: actual), expected: "nil")
  116. }
  117. }
  118. /// Matches if the value is not `nil`.
  119. static func notNil<Value>(_ matcher: Matcher<Value>? = nil) -> Matcher<Value?> {
  120. return .init { actual in
  121. if let actual = actual {
  122. return matcher?.evaluate(actual) ?? .match
  123. } else {
  124. return .noMatch(actual: "nil", expected: "not nil")
  125. }
  126. }
  127. }
  128. // MARK: Type
  129. /// Checks that the actual value is an instance of the given type.
  130. static func instanceOf<Value, Expected>(_: Expected.Type) -> Matcher<Value> {
  131. return .init { actual in
  132. if actual is Expected {
  133. return .match
  134. } else {
  135. return .noMatch(
  136. actual: String(describing: type(of: actual)) + " (\(actual))",
  137. expected: "value of type \(Expected.self)"
  138. )
  139. }
  140. }
  141. }
  142. // MARK: Collection
  143. /// Checks whether the collection has the expected count.
  144. static func hasCount<C: Collection>(_ count: Int) -> Matcher<C> {
  145. return .init { actual in
  146. actual.count == count
  147. ? .match
  148. : .noMatch(actual: "has count \(actual.count)", expected: "count of \(count)")
  149. }
  150. }
  151. static func isEmpty<C: Collection>() -> Matcher<C> {
  152. return .init { actual in
  153. actual.isEmpty
  154. ? .match
  155. : .noMatch(actual: "has \(actual.count) items", expected: "is empty")
  156. }
  157. }
  158. // MARK: gRPC matchers
  159. static func hasCode(_ code: GRPCStatus.Code) -> Matcher<GRPCStatus> {
  160. return .init { actual in
  161. actual.code == code
  162. ? .match
  163. : .noMatch(actual: "has status code \(actual)", expected: "\(code)")
  164. }
  165. }
  166. static func metadata<Request>(
  167. _ matcher: Matcher<HPACKHeaders>? = nil
  168. ) -> Matcher<GRPCServerRequestPart<Request>> {
  169. return .init { actual in
  170. switch actual {
  171. case let .metadata(headers):
  172. return matcher?.evaluate(headers) ?? .match
  173. default:
  174. return .noMatch(actual: String(describing: actual), expected: "metadata")
  175. }
  176. }
  177. }
  178. static func message<Request>(
  179. _ matcher: Matcher<Request>? = nil
  180. ) -> Matcher<GRPCServerRequestPart<Request>> {
  181. return .init { actual in
  182. switch actual {
  183. case let .message(message):
  184. return matcher?.evaluate(message) ?? .match
  185. default:
  186. return .noMatch(actual: String(describing: actual), expected: "message")
  187. }
  188. }
  189. }
  190. static func metadata<Response>(
  191. _ matcher: Matcher<HPACKHeaders>? = nil
  192. ) -> Matcher<GRPCServerResponsePart<Response>> {
  193. return .init { actual in
  194. switch actual {
  195. case let .metadata(headers):
  196. return matcher?.evaluate(headers) ?? .match
  197. default:
  198. return .noMatch(actual: String(describing: actual), expected: "metadata")
  199. }
  200. }
  201. }
  202. static func message<Response>(
  203. _ matcher: Matcher<Response>? = nil
  204. ) -> Matcher<GRPCServerResponsePart<Response>> {
  205. return .init { actual in
  206. switch actual {
  207. case let .message(message, _):
  208. return matcher?.evaluate(message) ?? .match
  209. default:
  210. return .noMatch(actual: String(describing: actual), expected: "message")
  211. }
  212. }
  213. }
  214. static func end<Response>(
  215. status statusMatcher: Matcher<GRPCStatus>? = nil,
  216. trailers trailersMatcher: Matcher<HPACKHeaders>? = nil
  217. ) -> Matcher<GRPCServerResponsePart<Response>> {
  218. return .init { actual in
  219. switch actual {
  220. case let .end(status, trailers):
  221. let statusMatch = (statusMatcher?.evaluate(status) ?? .match)
  222. switch statusMatcher?.evaluate(status) ?? .match {
  223. case .match:
  224. return trailersMatcher?.evaluate(trailers) ?? .match
  225. case .noMatch:
  226. return statusMatch
  227. }
  228. default:
  229. return .noMatch(actual: String(describing: actual), expected: "end")
  230. }
  231. }
  232. }
  233. static func headers<Response>(
  234. _ matcher: Matcher<HPACKHeaders>? = nil
  235. ) -> Matcher<_GRPCServerResponsePart<Response>> {
  236. return .init { actual in
  237. switch actual {
  238. case let .headers(headers):
  239. return matcher?.evaluate(headers) ?? .match
  240. default:
  241. return .noMatch(actual: String(describing: actual), expected: "headers")
  242. }
  243. }
  244. }
  245. static func message<Response>(
  246. _ matcher: Matcher<Response>? = nil
  247. ) -> Matcher<_GRPCServerResponsePart<Response>> {
  248. return .init { actual in
  249. switch actual {
  250. case let .message(message):
  251. return matcher?.evaluate(message.message) ?? .match
  252. default:
  253. return .noMatch(actual: String(describing: actual), expected: "message")
  254. }
  255. }
  256. }
  257. static func end<Response>(
  258. _ matcher: Matcher<GRPCStatus>? = nil
  259. ) -> Matcher<_GRPCServerResponsePart<Response>> {
  260. return .init { actual in
  261. switch actual {
  262. case let .statusAndTrailers(status, _):
  263. return matcher?.evaluate(status) ?? .match
  264. default:
  265. return .noMatch(actual: String(describing: actual), expected: "end")
  266. }
  267. }
  268. }
  269. // MARK: HTTP/2
  270. static func contains(
  271. _ name: String,
  272. _ matcher: Matcher<[String]>? = nil
  273. ) -> Matcher<HPACKHeaders> {
  274. return .init { actual in
  275. let headers = actual[name]
  276. if headers.isEmpty {
  277. return .noMatch(actual: "does not contain '\(name)'", expected: "contains '\(name)'")
  278. } else {
  279. return matcher?.evaluate(headers) ?? .match
  280. }
  281. }
  282. }
  283. }
  284. struct ExpressionMatcher<Value> {
  285. typealias Expression = () throws -> Value
  286. private typealias Evaluator = (Expression) -> MatchResult
  287. private var evaluator: Evaluator
  288. private init(_ evaluator: @escaping Evaluator) {
  289. self.evaluator = evaluator
  290. }
  291. fileprivate func evaluate(_ expression: Expression) -> MatchResult {
  292. return self.evaluator(expression)
  293. }
  294. /// Asserts that the expression does not throw and error. Returns the result of any provided
  295. /// matcher on the result of the expression.
  296. static func doesNotThrow<Value>(_ matcher: Matcher<Value>? = nil) -> ExpressionMatcher<Value> {
  297. return .init { expression in
  298. do {
  299. let value = try expression()
  300. return matcher?.evaluate(value) ?? .match
  301. } catch {
  302. return .noMatch(actual: "threw '\(error)'", expected: "should not throw error")
  303. }
  304. }
  305. }
  306. /// Asserts that the expression throws and error. Returns the result of any provided matcher
  307. /// on the error thrown by the expression.
  308. static func `throws`<Value>(_ matcher: Matcher<Error>? = nil) -> ExpressionMatcher<Value> {
  309. return .init { expression in
  310. do {
  311. let value = try expression()
  312. return .noMatch(actual: "returned '\(value)'", expected: "should throw error")
  313. } catch {
  314. return matcher?.evaluate(error) ?? .match
  315. }
  316. }
  317. }
  318. }