XCTestHelpers.swift 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239
  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 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<Value?> {
  120. return .init { actual in
  121. actual != nil
  122. ? .match
  123. : .noMatch(actual: "nil", expected: "not nil")
  124. }
  125. }
  126. // MARK: Type
  127. /// Checks that the actual value is an instance of the given type.
  128. static func instanceOf<Value, Expected>(_: Expected.Type) -> Matcher<Value> {
  129. return .init { actual in
  130. if actual is Expected {
  131. return .match
  132. } else {
  133. return .noMatch(
  134. actual: String(describing: type(of: actual)) + " (\(actual))",
  135. expected: "value of type \(Expected.self)"
  136. )
  137. }
  138. }
  139. }
  140. // MARK: Collection
  141. /// Checks whether the collection has the expected count.
  142. static func hasCount<C: Collection>(_ count: Int) -> Matcher<C> {
  143. return .init { actual in
  144. actual.count == count
  145. ? .match
  146. : .noMatch(actual: "has count \(actual)", expected: "count of \(count)")
  147. }
  148. }
  149. // MARK: gRPC matchers
  150. static func hasCode(_ code: GRPCStatus.Code) -> Matcher<GRPCStatus> {
  151. return .init { actual in
  152. actual.code == code
  153. ? .match
  154. : .noMatch(actual: "has status code \(actual)", expected: "\(code)")
  155. }
  156. }
  157. // MARK: HTTP/2
  158. static func contains(
  159. _ name: String,
  160. _ matcher: Matcher<[String]>? = nil
  161. ) -> Matcher<HPACKHeaders> {
  162. return .init { actual in
  163. let headers = actual[name]
  164. if headers.isEmpty {
  165. return .noMatch(actual: "does not contain '\(name)'", expected: "contains '\(name)'")
  166. } else {
  167. return matcher?.evaluate(headers) ?? .match
  168. }
  169. }
  170. }
  171. }
  172. struct ExpressionMatcher<Value> {
  173. typealias Expression = () throws -> Value
  174. private typealias Evaluator = (Expression) -> MatchResult
  175. private var evaluator: Evaluator
  176. private init(_ evaluator: @escaping Evaluator) {
  177. self.evaluator = evaluator
  178. }
  179. fileprivate func evaluate(_ expression: Expression) -> MatchResult {
  180. return self.evaluator(expression)
  181. }
  182. /// Asserts that the expression does not throw and error. Returns the result of any provided
  183. /// matcher on the result of the expression.
  184. static func doesNotThrow<Value>(_ matcher: Matcher<Value>? = nil) -> ExpressionMatcher<Value> {
  185. return .init { expression in
  186. do {
  187. let value = try expression()
  188. return matcher?.evaluate(value) ?? .match
  189. } catch {
  190. return .noMatch(actual: "threw '\(error)'", expected: "should not throw error")
  191. }
  192. }
  193. }
  194. /// Asserts that the expression throws and error. Returns the result of any provided matcher
  195. /// on the error thrown by the expression.
  196. static func `throws`<Value>(_ matcher: Matcher<Error>? = nil) -> ExpressionMatcher<Value> {
  197. return .init { expression in
  198. do {
  199. let value = try expression()
  200. return .noMatch(actual: "returned '\(value)'", expected: "should throw error")
  201. } catch {
  202. return matcher?.evaluate(error) ?? .match
  203. }
  204. }
  205. }
  206. }