MethodConfigCodingTests.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430
  1. /*
  2. * Copyright 2024, 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 Foundation
  17. import SwiftProtobuf
  18. import XCTest
  19. @testable import GRPCCore
  20. @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
  21. internal final class MethodConfigCodingTests: XCTestCase {
  22. private let encoder = JSONEncoder()
  23. private let decoder = JSONDecoder()
  24. private func testDecodeThrowsRuntimeError<D: Decodable>(json: String, as: D.Type) throws {
  25. XCTAssertThrowsError(
  26. ofType: RuntimeError.self,
  27. try self.decoder.decode(D.self, from: Data(json.utf8))
  28. ) { error in
  29. XCTAssertEqual(error.code, .invalidArgument)
  30. }
  31. }
  32. func testDecodeMethodConfigName() throws {
  33. let inputs: [(String, MethodConfig.Name)] = [
  34. (#"{"service": "foo.bar", "method": "baz"}"#, .init(service: "foo.bar", method: "baz")),
  35. (#"{"service": "foo.bar"}"#, .init(service: "foo.bar", method: "")),
  36. (#"{}"#, .init(service: "", method: "")),
  37. ]
  38. for (json, expected) in inputs {
  39. let decoded = try self.decoder.decode(MethodConfig.Name.self, from: Data(json.utf8))
  40. XCTAssertEqual(decoded, expected)
  41. }
  42. }
  43. func testEncodeDecodeMethodConfigName() throws {
  44. let inputs: [MethodConfig.Name] = [
  45. MethodConfig.Name(service: "foo.bar", method: "baz"),
  46. MethodConfig.Name(service: "foo.bar", method: ""),
  47. MethodConfig.Name(service: "", method: ""),
  48. ]
  49. // We can't do encode-only tests as the output is non-deterministic (the ordering of
  50. // service/method in the JSON object)
  51. for name in inputs {
  52. let encoded = try self.encoder.encode(name)
  53. let decoded = try self.decoder.decode(MethodConfig.Name.self, from: encoded)
  54. XCTAssertEqual(decoded, name)
  55. }
  56. }
  57. func testDecodeProtobufDuration() throws {
  58. let inputs: [(String, Duration)] = [
  59. ("1.0s", .seconds(1)),
  60. ("1s", .seconds(1)),
  61. ("1.000000s", .seconds(1)),
  62. ("0s", .zero),
  63. ("100.123s", .milliseconds(100_123)),
  64. ]
  65. for (input, expected) in inputs {
  66. let json = "\"\(input)\""
  67. let protoDuration = try self.decoder.decode(
  68. GoogleProtobufDuration.self,
  69. from: Data(json.utf8)
  70. )
  71. let components = protoDuration.duration.components
  72. // Conversion is lossy as we go from floating point seconds to integer seconds and
  73. // attoseconds. Allow for millisecond precision.
  74. let divisor: Int64 = 1_000_000_000_000_000
  75. XCTAssertEqual(components.seconds, expected.components.seconds)
  76. XCTAssertEqual(components.attoseconds / divisor, expected.components.attoseconds / divisor)
  77. }
  78. }
  79. func testEncodeProtobufDuration() throws {
  80. let inputs: [(Duration, String)] = [
  81. (.seconds(1), "\"1.0s\""),
  82. (.zero, "\"0.0s\""),
  83. (.milliseconds(100_123), "\"100.123s\""),
  84. ]
  85. for (input, expected) in inputs {
  86. let duration = GoogleProtobufDuration(duration: input)
  87. let encoded = try self.encoder.encode(duration)
  88. let json = String(decoding: encoded, as: UTF8.self)
  89. XCTAssertEqual(json, expected)
  90. }
  91. }
  92. func testDecodeInvalidProtobufDuration() throws {
  93. for timestamp in ["1", "1ss", "1S", "1.0S"] {
  94. let json = "\"\(timestamp)\""
  95. try self.testDecodeThrowsRuntimeError(json: json, as: GoogleProtobufDuration.self)
  96. }
  97. }
  98. func testDecodeRPCCodeFromCaseName() throws {
  99. let inputs: [(String, Status.Code)] = [
  100. ("OK", .ok),
  101. ("CANCELLED", .cancelled),
  102. ("UNKNOWN", .unknown),
  103. ("INVALID_ARGUMENT", .invalidArgument),
  104. ("DEADLINE_EXCEEDED", .deadlineExceeded),
  105. ("NOT_FOUND", .notFound),
  106. ("ALREADY_EXISTS", .alreadyExists),
  107. ("PERMISSION_DENIED", .permissionDenied),
  108. ("RESOURCE_EXHAUSTED", .resourceExhausted),
  109. ("FAILED_PRECONDITION", .failedPrecondition),
  110. ("ABORTED", .aborted),
  111. ("OUT_OF_RANGE", .outOfRange),
  112. ("UNIMPLEMENTED", .unimplemented),
  113. ("INTERNAL", .internalError),
  114. ("UNAVAILABLE", .unavailable),
  115. ("DATA_LOSS", .dataLoss),
  116. ("UNAUTHENTICATED", .unauthenticated),
  117. ]
  118. for (name, expected) in inputs {
  119. let json = "\"\(name)\""
  120. let code = try self.decoder.decode(GoogleRPCCode.self, from: Data(json.utf8))
  121. XCTAssertEqual(code.code, expected)
  122. }
  123. }
  124. func testDecodeRPCCodeFromRawValue() throws {
  125. let inputs: [(Int, Status.Code)] = [
  126. (0, .ok),
  127. (1, .cancelled),
  128. (2, .unknown),
  129. (3, .invalidArgument),
  130. (4, .deadlineExceeded),
  131. (5, .notFound),
  132. (6, .alreadyExists),
  133. (7, .permissionDenied),
  134. (8, .resourceExhausted),
  135. (9, .failedPrecondition),
  136. (10, .aborted),
  137. (11, .outOfRange),
  138. (12, .unimplemented),
  139. (13, .internalError),
  140. (14, .unavailable),
  141. (15, .dataLoss),
  142. (16, .unauthenticated),
  143. ]
  144. for (rawValue, expected) in inputs {
  145. let json = "\(rawValue)"
  146. let code = try self.decoder.decode(GoogleRPCCode.self, from: Data(json.utf8))
  147. XCTAssertEqual(code.code, expected)
  148. }
  149. }
  150. func testEncodeDecodeRPCCode() throws {
  151. let codes: [Status.Code] = [
  152. .ok,
  153. .cancelled,
  154. .unknown,
  155. .invalidArgument,
  156. .deadlineExceeded,
  157. .notFound,
  158. .alreadyExists,
  159. .permissionDenied,
  160. .resourceExhausted,
  161. .failedPrecondition,
  162. .aborted,
  163. .outOfRange,
  164. .unimplemented,
  165. .internalError,
  166. .unavailable,
  167. .dataLoss,
  168. .unauthenticated,
  169. ]
  170. for code in codes {
  171. let encoded = try self.encoder.encode(GoogleRPCCode(code: code))
  172. let decoded = try self.decoder.decode(GoogleRPCCode.self, from: encoded)
  173. XCTAssertEqual(decoded.code, code)
  174. }
  175. }
  176. func testDecodeRetryPolicy() throws {
  177. let json = """
  178. {
  179. "maxAttempts": 3,
  180. "initialBackoff": "1s",
  181. "maxBackoff": "3s",
  182. "backoffMultiplier": 1.6,
  183. "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"]
  184. }
  185. """
  186. let expected = RetryPolicy(
  187. maximumAttempts: 3,
  188. initialBackoff: .seconds(1),
  189. maximumBackoff: .seconds(3),
  190. backoffMultiplier: 1.6,
  191. retryableStatusCodes: [.aborted, .unavailable]
  192. )
  193. let decoded = try self.decoder.decode(RetryPolicy.self, from: Data(json.utf8))
  194. XCTAssertEqual(decoded, expected)
  195. }
  196. func testEncodeDecodeRetryPolicy() throws {
  197. let policy = RetryPolicy(
  198. maximumAttempts: 3,
  199. initialBackoff: .seconds(1),
  200. maximumBackoff: .seconds(3),
  201. backoffMultiplier: 1.6,
  202. retryableStatusCodes: [.aborted]
  203. )
  204. let encoded = try self.encoder.encode(policy)
  205. let decoded = try self.decoder.decode(RetryPolicy.self, from: encoded)
  206. XCTAssertEqual(decoded, policy)
  207. }
  208. func testDecodeRetryPolicyWithInvalidRetryMaxAttempts() throws {
  209. let cases = ["-1", "0", "1"]
  210. for maxAttempts in cases {
  211. let json = """
  212. {
  213. "maxAttempts": \(maxAttempts),
  214. "initialBackoff": "1s",
  215. "maxBackoff": "3s",
  216. "backoffMultiplier": 1.6,
  217. "retryableStatusCodes": ["ABORTED"]
  218. }
  219. """
  220. try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
  221. }
  222. }
  223. func testDecodeRetryPolicyWithInvalidInitialBackoff() throws {
  224. let cases = ["0s", "-1s"]
  225. for backoff in cases {
  226. let json = """
  227. {
  228. "maxAttempts": 3,
  229. "initialBackoff": "\(backoff)",
  230. "maxBackoff": "3s",
  231. "backoffMultiplier": 1.6,
  232. "retryableStatusCodes": ["ABORTED"]
  233. }
  234. """
  235. try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
  236. }
  237. }
  238. func testDecodeRetryPolicyWithInvalidMaxBackoff() throws {
  239. let cases = ["0s", "-1s"]
  240. for backoff in cases {
  241. let json = """
  242. {
  243. "maxAttempts": 3,
  244. "initialBackoff": "1s",
  245. "maxBackoff": "\(backoff)",
  246. "backoffMultiplier": 1.6,
  247. "retryableStatusCodes": ["ABORTED"]
  248. }
  249. """
  250. try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
  251. }
  252. }
  253. func testDecodeRetryPolicyWithInvalidBackoffMultiplier() throws {
  254. let cases = ["0", "-1.5"]
  255. for multiplier in cases {
  256. let json = """
  257. {
  258. "maxAttempts": 3,
  259. "initialBackoff": "1s",
  260. "maxBackoff": "3s",
  261. "backoffMultiplier": \(multiplier),
  262. "retryableStatusCodes": ["ABORTED"]
  263. }
  264. """
  265. try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
  266. }
  267. }
  268. func testDecodeRetryPolicyWithEmptyRetryableStatusCodes() throws {
  269. let json = """
  270. {
  271. "maxAttempts": 3,
  272. "initialBackoff": "1s",
  273. "maxBackoff": "3s",
  274. "backoffMultiplier": 1,
  275. "retryableStatusCodes": []
  276. }
  277. """
  278. try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
  279. }
  280. func testDecodeHedgingPolicy() throws {
  281. let json = """
  282. {
  283. "maxAttempts": 3,
  284. "hedgingDelay": "1s",
  285. "nonFatalStatusCodes": ["ABORTED"]
  286. }
  287. """
  288. let expected = HedgingPolicy(
  289. maximumAttempts: 3,
  290. hedgingDelay: .seconds(1),
  291. nonFatalStatusCodes: [.aborted]
  292. )
  293. let decoded = try self.decoder.decode(HedgingPolicy.self, from: Data(json.utf8))
  294. XCTAssertEqual(decoded, expected)
  295. }
  296. func testEncodeDecodeHedgingPolicy() throws {
  297. let policy = HedgingPolicy(
  298. maximumAttempts: 3,
  299. hedgingDelay: .seconds(1),
  300. nonFatalStatusCodes: [.aborted]
  301. )
  302. let encoded = try self.encoder.encode(policy)
  303. let decoded = try self.decoder.decode(HedgingPolicy.self, from: encoded)
  304. XCTAssertEqual(decoded, policy)
  305. }
  306. func testMethodConfigDecodeFromJSON() throws {
  307. let config = Grpc_ServiceConfig_MethodConfig.with {
  308. $0.name = [
  309. .with {
  310. $0.service = "echo.Echo"
  311. $0.method = "Get"
  312. }
  313. ]
  314. $0.waitForReady = true
  315. $0.timeout = .with {
  316. $0.seconds = 1
  317. $0.nanos = 0
  318. }
  319. $0.maxRequestMessageBytes = 1024
  320. $0.maxResponseMessageBytes = 2048
  321. }
  322. // Test the 'regular' config.
  323. do {
  324. let jsonConfig = try config.jsonUTF8Data()
  325. let decoded = try self.decoder.decode(MethodConfig.self, from: jsonConfig)
  326. XCTAssertEqual(decoded.names, [MethodConfig.Name(service: "echo.Echo", method: "Get")])
  327. XCTAssertEqual(decoded.waitForReady, true)
  328. XCTAssertEqual(decoded.timeout, Duration(secondsComponent: 1, attosecondsComponent: 0))
  329. XCTAssertEqual(decoded.maxRequestMessageBytes, 1024)
  330. XCTAssertEqual(decoded.maxResponseMessageBytes, 2048)
  331. XCTAssertNil(decoded.executionPolicy)
  332. }
  333. // Test the hedging policy.
  334. do {
  335. var config = config
  336. config.hedgingPolicy = .with {
  337. $0.maxAttempts = 3
  338. $0.hedgingDelay = .with { $0.seconds = 42 }
  339. $0.nonFatalStatusCodes = [
  340. .aborted,
  341. .unimplemented,
  342. ]
  343. }
  344. let jsonConfig = try config.jsonUTF8Data()
  345. let decoded = try self.decoder.decode(MethodConfig.self, from: jsonConfig)
  346. switch decoded.executionPolicy?.wrapped {
  347. case let .some(.hedge(policy)):
  348. XCTAssertEqual(policy.maximumAttempts, 3)
  349. XCTAssertEqual(policy.hedgingDelay, .seconds(42))
  350. XCTAssertEqual(policy.nonFatalStatusCodes, [.aborted, .unimplemented])
  351. default:
  352. XCTFail("Expected hedging policy")
  353. }
  354. }
  355. // Test the retry policy.
  356. do {
  357. var config = config
  358. config.retryPolicy = .with {
  359. $0.maxAttempts = 3
  360. $0.initialBackoff = .with { $0.seconds = 1 }
  361. $0.maxBackoff = .with { $0.seconds = 3 }
  362. $0.backoffMultiplier = 1.6
  363. $0.retryableStatusCodes = [
  364. .aborted,
  365. .unimplemented,
  366. ]
  367. }
  368. let jsonConfig = try config.jsonUTF8Data()
  369. let decoded = try self.decoder.decode(MethodConfig.self, from: jsonConfig)
  370. switch decoded.executionPolicy?.wrapped {
  371. case let .some(.retry(policy)):
  372. XCTAssertEqual(policy.maximumAttempts, 3)
  373. XCTAssertEqual(policy.initialBackoff, .seconds(1))
  374. XCTAssertEqual(policy.maximumBackoff, .seconds(3))
  375. XCTAssertEqual(policy.backoffMultiplier, 1.6)
  376. XCTAssertEqual(policy.retryableStatusCodes, [.aborted, .unimplemented])
  377. default:
  378. XCTFail("Expected hedging policy")
  379. }
  380. }
  381. }
  382. }