ServiceConfigCodingTests.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  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 GRPCCore
  18. import XCTest
  19. final class ServiceConfigCodingTests: XCTestCase {
  20. private let encoder = JSONEncoder()
  21. private let decoder = JSONDecoder()
  22. private func testDecodeThrowsRuntimeError<D: Decodable>(json: String, as: D.Type) throws {
  23. XCTAssertThrowsError(
  24. ofType: RuntimeError.self,
  25. try self.decoder.decode(D.self, from: Data(json.utf8))
  26. ) { error in
  27. XCTAssertEqual(error.code, .invalidArgument)
  28. }
  29. }
  30. private func testRoundTripEncodeDecode<C: Codable & Equatable>(_ value: C) throws {
  31. let encoded = try self.encoder.encode(value)
  32. let decoded = try self.decoder.decode(C.self, from: encoded)
  33. XCTAssertEqual(decoded, value)
  34. }
  35. func testDecodeRetryThrottlingPolicy() throws {
  36. let json = """
  37. {
  38. "maxTokens": 10,
  39. "tokenRatio": 0.5
  40. }
  41. """
  42. let expected = try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 0.5)
  43. let policy = try self.decoder.decode(
  44. ServiceConfig.RetryThrottling.self,
  45. from: Data(json.utf8)
  46. )
  47. XCTAssertEqual(policy, expected)
  48. }
  49. func testEncodeDecodeRetryThrottlingPolicy() throws {
  50. let policy = try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 0.5)
  51. try self.testRoundTripEncodeDecode(policy)
  52. }
  53. func testDecodeRetryThrottlingPolicyWithInvalidTokens() throws {
  54. let inputs = ["0", "-1", "-42"]
  55. for input in inputs {
  56. let json = """
  57. {
  58. "maxTokens": \(input),
  59. "tokenRatio": 0.5
  60. }
  61. """
  62. try self.testDecodeThrowsRuntimeError(
  63. json: json,
  64. as: ServiceConfig.RetryThrottling.self
  65. )
  66. }
  67. }
  68. func testDecodeRetryThrottlingPolicyWithInvalidTokenRatio() throws {
  69. let inputs = ["0.0", "-1.0", "-42"]
  70. for input in inputs {
  71. let json = """
  72. {
  73. "maxTokens": 10,
  74. "tokenRatio": \(input)
  75. }
  76. """
  77. try self.testDecodeThrowsRuntimeError(
  78. json: json,
  79. as: ServiceConfig.RetryThrottling.self
  80. )
  81. }
  82. }
  83. func testDecodePickFirstPolicy() throws {
  84. let inputs: [(String, ServiceConfig.LoadBalancingConfig.PickFirst)] = [
  85. (#"{"shuffleAddressList": true}"#, .init(shuffleAddressList: true)),
  86. (#"{"shuffleAddressList": false}"#, .init(shuffleAddressList: false)),
  87. (#"{}"#, .init(shuffleAddressList: false)),
  88. ]
  89. for (input, expected) in inputs {
  90. let pickFirst = try self.decoder.decode(
  91. ServiceConfig.LoadBalancingConfig.PickFirst.self,
  92. from: Data(input.utf8)
  93. )
  94. XCTAssertEqual(pickFirst, expected)
  95. }
  96. }
  97. func testEncodePickFirstPolicy() throws {
  98. let inputs: [(ServiceConfig.LoadBalancingConfig.PickFirst, String)] = [
  99. (.init(shuffleAddressList: true), #"{"shuffleAddressList":true}"#),
  100. (.init(shuffleAddressList: false), #"{"shuffleAddressList":false}"#),
  101. ]
  102. for (input, expected) in inputs {
  103. let encoded = try self.encoder.encode(input)
  104. XCTAssertEqual(String(decoding: encoded, as: UTF8.self), expected)
  105. }
  106. }
  107. func testDecodeRoundRobinPolicy() throws {
  108. let json = "{}"
  109. let policy = try self.decoder.decode(
  110. ServiceConfig.LoadBalancingConfig.RoundRobin.self,
  111. from: Data(json.utf8)
  112. )
  113. XCTAssertEqual(policy, ServiceConfig.LoadBalancingConfig.RoundRobin())
  114. }
  115. func testEncodeRoundRobinPolicy() throws {
  116. let policy = ServiceConfig.LoadBalancingConfig.RoundRobin()
  117. let encoded = try self.encoder.encode(policy)
  118. XCTAssertEqual(String(decoding: encoded, as: UTF8.self), "{}")
  119. }
  120. func testDecodeLoadBalancingConfiguration() throws {
  121. let inputs: [(String, ServiceConfig.LoadBalancingConfig)] = [
  122. (#"{"round_robin": {}}"#, .roundRobin),
  123. (#"{"pick_first": {}}"#, .pickFirst(shuffleAddressList: false)),
  124. (#"{"pick_first": {"shuffleAddressList": false}}"#, .pickFirst(shuffleAddressList: false)),
  125. ]
  126. for (input, expected) in inputs {
  127. let decoded = try self.decoder.decode(
  128. ServiceConfig.LoadBalancingConfig.self,
  129. from: Data(input.utf8)
  130. )
  131. XCTAssertEqual(decoded, expected)
  132. }
  133. }
  134. func testEncodeLoadBalancingConfiguration() throws {
  135. let inputs: [(ServiceConfig.LoadBalancingConfig, String)] = [
  136. (.roundRobin, #"{"round_robin":{}}"#),
  137. (.pickFirst(shuffleAddressList: false), #"{"pick_first":{"shuffleAddressList":false}}"#),
  138. ]
  139. for (input, expected) in inputs {
  140. let encoded = try self.encoder.encode(input)
  141. XCTAssertEqual(String(decoding: encoded, as: UTF8.self), expected)
  142. }
  143. }
  144. func testDecodeServiceConfigFromProtoJSON() throws {
  145. let serviceConfig = Grpc_ServiceConfig_ServiceConfig.with {
  146. $0.methodConfig = [
  147. Grpc_ServiceConfig_MethodConfig.with {
  148. $0.name = [
  149. Grpc_ServiceConfig_MethodConfig.Name.with {
  150. $0.service = "foo.Foo"
  151. $0.method = "Bar"
  152. }
  153. ]
  154. $0.timeout = .with { $0.seconds = 1 }
  155. $0.maxRequestMessageBytes = 123
  156. $0.maxResponseMessageBytes = 456
  157. }
  158. ]
  159. $0.loadBalancingConfig = [
  160. .with { $0.roundRobin = .init() },
  161. .with { $0.pickFirst = .with { $0.shuffleAddressList = true } },
  162. ]
  163. $0.retryThrottling = .with {
  164. $0.maxTokens = 10
  165. $0.tokenRatio = 0.1
  166. }
  167. }
  168. let encoded = try serviceConfig.jsonUTF8Data()
  169. let decoded = try self.decoder.decode(ServiceConfig.self, from: encoded)
  170. let expected = ServiceConfig(
  171. methodConfig: [
  172. MethodConfig(
  173. names: [
  174. MethodConfig.Name(service: "foo.Foo", method: "Bar")
  175. ],
  176. timeout: .seconds(1),
  177. maxRequestMessageBytes: 123,
  178. maxResponseMessageBytes: 456
  179. )
  180. ],
  181. loadBalancingConfig: [
  182. .roundRobin,
  183. .pickFirst(shuffleAddressList: true),
  184. ],
  185. retryThrottling: try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 0.1)
  186. )
  187. XCTAssertEqual(decoded, expected)
  188. }
  189. func testEncodeAndDecodeServiceConfig() throws {
  190. let serviceConfig = ServiceConfig(
  191. methodConfig: [
  192. MethodConfig(
  193. names: [
  194. MethodConfig.Name(service: "echo.Echo", method: "Get"),
  195. MethodConfig.Name(service: "greeter.HelloWorld"),
  196. ],
  197. timeout: .seconds(42),
  198. maxRequestMessageBytes: 2048,
  199. maxResponseMessageBytes: 4096,
  200. executionPolicy: .hedge(
  201. HedgingPolicy(
  202. maxAttempts: 3,
  203. hedgingDelay: .seconds(1),
  204. nonFatalStatusCodes: [.aborted]
  205. )
  206. )
  207. ),
  208. MethodConfig(
  209. names: [
  210. MethodConfig.Name(service: "echo.Echo", method: "Update")
  211. ],
  212. timeout: .seconds(300),
  213. maxRequestMessageBytes: 10_000
  214. ),
  215. ],
  216. loadBalancingConfig: [
  217. .pickFirst(shuffleAddressList: true),
  218. .roundRobin,
  219. ],
  220. retryThrottling: try ServiceConfig.RetryThrottling(maxTokens: 10, tokenRatio: 3.141)
  221. )
  222. try self.testRoundTripEncodeDecode(serviceConfig)
  223. }
  224. }