ServiceConfigCodingTests.swift 7.6 KB

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