ServiceConfigCodingTests.swift 7.6 KB

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