| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430 |
- /*
- * Copyright 2024, gRPC Authors All rights reserved.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- import Foundation
- import SwiftProtobuf
- import XCTest
- @testable import GRPCCore
- @available(macOS 13.0, iOS 16.0, watchOS 9.0, tvOS 16.0, *)
- internal final class MethodConfigCodingTests: XCTestCase {
- private let encoder = JSONEncoder()
- private let decoder = JSONDecoder()
- private func testDecodeThrowsRuntimeError<D: Decodable>(json: String, as: D.Type) throws {
- XCTAssertThrowsError(
- ofType: RuntimeError.self,
- try self.decoder.decode(D.self, from: Data(json.utf8))
- ) { error in
- XCTAssertEqual(error.code, .invalidArgument)
- }
- }
- func testDecodeMethodConfigName() throws {
- let inputs: [(String, MethodConfig.Name)] = [
- (#"{"service": "foo.bar", "method": "baz"}"#, .init(service: "foo.bar", method: "baz")),
- (#"{"service": "foo.bar"}"#, .init(service: "foo.bar", method: "")),
- (#"{}"#, .init(service: "", method: "")),
- ]
- for (json, expected) in inputs {
- let decoded = try self.decoder.decode(MethodConfig.Name.self, from: Data(json.utf8))
- XCTAssertEqual(decoded, expected)
- }
- }
- func testEncodeDecodeMethodConfigName() throws {
- let inputs: [MethodConfig.Name] = [
- MethodConfig.Name(service: "foo.bar", method: "baz"),
- MethodConfig.Name(service: "foo.bar", method: ""),
- MethodConfig.Name(service: "", method: ""),
- ]
- // We can't do encode-only tests as the output is non-deterministic (the ordering of
- // service/method in the JSON object)
- for name in inputs {
- let encoded = try self.encoder.encode(name)
- let decoded = try self.decoder.decode(MethodConfig.Name.self, from: encoded)
- XCTAssertEqual(decoded, name)
- }
- }
- func testDecodeProtobufDuration() throws {
- let inputs: [(String, Duration)] = [
- ("1.0s", .seconds(1)),
- ("1s", .seconds(1)),
- ("1.000000s", .seconds(1)),
- ("0s", .zero),
- ("100.123s", .milliseconds(100_123)),
- ]
- for (input, expected) in inputs {
- let json = "\"\(input)\""
- let protoDuration = try self.decoder.decode(
- GoogleProtobufDuration.self,
- from: Data(json.utf8)
- )
- let components = protoDuration.duration.components
- // Conversion is lossy as we go from floating point seconds to integer seconds and
- // attoseconds. Allow for millisecond precision.
- let divisor: Int64 = 1_000_000_000_000_000
- XCTAssertEqual(components.seconds, expected.components.seconds)
- XCTAssertEqual(components.attoseconds / divisor, expected.components.attoseconds / divisor)
- }
- }
- func testEncodeProtobufDuration() throws {
- let inputs: [(Duration, String)] = [
- (.seconds(1), "\"1.0s\""),
- (.zero, "\"0.0s\""),
- (.milliseconds(100_123), "\"100.123s\""),
- ]
- for (input, expected) in inputs {
- let duration = GoogleProtobufDuration(duration: input)
- let encoded = try self.encoder.encode(duration)
- let json = String(decoding: encoded, as: UTF8.self)
- XCTAssertEqual(json, expected)
- }
- }
- func testDecodeInvalidProtobufDuration() throws {
- for timestamp in ["1", "1ss", "1S", "1.0S"] {
- let json = "\"\(timestamp)\""
- try self.testDecodeThrowsRuntimeError(json: json, as: GoogleProtobufDuration.self)
- }
- }
- func testDecodeRPCCodeFromCaseName() throws {
- let inputs: [(String, Status.Code)] = [
- ("OK", .ok),
- ("CANCELLED", .cancelled),
- ("UNKNOWN", .unknown),
- ("INVALID_ARGUMENT", .invalidArgument),
- ("DEADLINE_EXCEEDED", .deadlineExceeded),
- ("NOT_FOUND", .notFound),
- ("ALREADY_EXISTS", .alreadyExists),
- ("PERMISSION_DENIED", .permissionDenied),
- ("RESOURCE_EXHAUSTED", .resourceExhausted),
- ("FAILED_PRECONDITION", .failedPrecondition),
- ("ABORTED", .aborted),
- ("OUT_OF_RANGE", .outOfRange),
- ("UNIMPLEMENTED", .unimplemented),
- ("INTERNAL", .internalError),
- ("UNAVAILABLE", .unavailable),
- ("DATA_LOSS", .dataLoss),
- ("UNAUTHENTICATED", .unauthenticated),
- ]
- for (name, expected) in inputs {
- let json = "\"\(name)\""
- let code = try self.decoder.decode(GoogleRPCCode.self, from: Data(json.utf8))
- XCTAssertEqual(code.code, expected)
- }
- }
- func testDecodeRPCCodeFromRawValue() throws {
- let inputs: [(Int, Status.Code)] = [
- (0, .ok),
- (1, .cancelled),
- (2, .unknown),
- (3, .invalidArgument),
- (4, .deadlineExceeded),
- (5, .notFound),
- (6, .alreadyExists),
- (7, .permissionDenied),
- (8, .resourceExhausted),
- (9, .failedPrecondition),
- (10, .aborted),
- (11, .outOfRange),
- (12, .unimplemented),
- (13, .internalError),
- (14, .unavailable),
- (15, .dataLoss),
- (16, .unauthenticated),
- ]
- for (rawValue, expected) in inputs {
- let json = "\(rawValue)"
- let code = try self.decoder.decode(GoogleRPCCode.self, from: Data(json.utf8))
- XCTAssertEqual(code.code, expected)
- }
- }
- func testEncodeDecodeRPCCode() throws {
- let codes: [Status.Code] = [
- .ok,
- .cancelled,
- .unknown,
- .invalidArgument,
- .deadlineExceeded,
- .notFound,
- .alreadyExists,
- .permissionDenied,
- .resourceExhausted,
- .failedPrecondition,
- .aborted,
- .outOfRange,
- .unimplemented,
- .internalError,
- .unavailable,
- .dataLoss,
- .unauthenticated,
- ]
- for code in codes {
- let encoded = try self.encoder.encode(GoogleRPCCode(code: code))
- let decoded = try self.decoder.decode(GoogleRPCCode.self, from: encoded)
- XCTAssertEqual(decoded.code, code)
- }
- }
- func testDecodeRetryPolicy() throws {
- let json = """
- {
- "maxAttempts": 3,
- "initialBackoff": "1s",
- "maxBackoff": "3s",
- "backoffMultiplier": 1.6,
- "retryableStatusCodes": ["ABORTED", "UNAVAILABLE"]
- }
- """
- let expected = RetryPolicy(
- maximumAttempts: 3,
- initialBackoff: .seconds(1),
- maximumBackoff: .seconds(3),
- backoffMultiplier: 1.6,
- retryableStatusCodes: [.aborted, .unavailable]
- )
- let decoded = try self.decoder.decode(RetryPolicy.self, from: Data(json.utf8))
- XCTAssertEqual(decoded, expected)
- }
- func testEncodeDecodeRetryPolicy() throws {
- let policy = RetryPolicy(
- maximumAttempts: 3,
- initialBackoff: .seconds(1),
- maximumBackoff: .seconds(3),
- backoffMultiplier: 1.6,
- retryableStatusCodes: [.aborted]
- )
- let encoded = try self.encoder.encode(policy)
- let decoded = try self.decoder.decode(RetryPolicy.self, from: encoded)
- XCTAssertEqual(decoded, policy)
- }
- func testDecodeRetryPolicyWithInvalidRetryMaxAttempts() throws {
- let cases = ["-1", "0", "1"]
- for maxAttempts in cases {
- let json = """
- {
- "maxAttempts": \(maxAttempts),
- "initialBackoff": "1s",
- "maxBackoff": "3s",
- "backoffMultiplier": 1.6,
- "retryableStatusCodes": ["ABORTED"]
- }
- """
- try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
- }
- }
- func testDecodeRetryPolicyWithInvalidInitialBackoff() throws {
- let cases = ["0s", "-1s"]
- for backoff in cases {
- let json = """
- {
- "maxAttempts": 3,
- "initialBackoff": "\(backoff)",
- "maxBackoff": "3s",
- "backoffMultiplier": 1.6,
- "retryableStatusCodes": ["ABORTED"]
- }
- """
- try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
- }
- }
- func testDecodeRetryPolicyWithInvalidMaxBackoff() throws {
- let cases = ["0s", "-1s"]
- for backoff in cases {
- let json = """
- {
- "maxAttempts": 3,
- "initialBackoff": "1s",
- "maxBackoff": "\(backoff)",
- "backoffMultiplier": 1.6,
- "retryableStatusCodes": ["ABORTED"]
- }
- """
- try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
- }
- }
- func testDecodeRetryPolicyWithInvalidBackoffMultiplier() throws {
- let cases = ["0", "-1.5"]
- for multiplier in cases {
- let json = """
- {
- "maxAttempts": 3,
- "initialBackoff": "1s",
- "maxBackoff": "3s",
- "backoffMultiplier": \(multiplier),
- "retryableStatusCodes": ["ABORTED"]
- }
- """
- try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
- }
- }
- func testDecodeRetryPolicyWithEmptyRetryableStatusCodes() throws {
- let json = """
- {
- "maxAttempts": 3,
- "initialBackoff": "1s",
- "maxBackoff": "3s",
- "backoffMultiplier": 1,
- "retryableStatusCodes": []
- }
- """
- try self.testDecodeThrowsRuntimeError(json: json, as: RetryPolicy.self)
- }
- func testDecodeHedgingPolicy() throws {
- let json = """
- {
- "maxAttempts": 3,
- "hedgingDelay": "1s",
- "nonFatalStatusCodes": ["ABORTED"]
- }
- """
- let expected = HedgingPolicy(
- maximumAttempts: 3,
- hedgingDelay: .seconds(1),
- nonFatalStatusCodes: [.aborted]
- )
- let decoded = try self.decoder.decode(HedgingPolicy.self, from: Data(json.utf8))
- XCTAssertEqual(decoded, expected)
- }
- func testEncodeDecodeHedgingPolicy() throws {
- let policy = HedgingPolicy(
- maximumAttempts: 3,
- hedgingDelay: .seconds(1),
- nonFatalStatusCodes: [.aborted]
- )
- let encoded = try self.encoder.encode(policy)
- let decoded = try self.decoder.decode(HedgingPolicy.self, from: encoded)
- XCTAssertEqual(decoded, policy)
- }
- func testMethodConfigDecodeFromJSON() throws {
- let config = Grpc_ServiceConfig_MethodConfig.with {
- $0.name = [
- .with {
- $0.service = "echo.Echo"
- $0.method = "Get"
- }
- ]
- $0.waitForReady = true
- $0.timeout = .with {
- $0.seconds = 1
- $0.nanos = 0
- }
- $0.maxRequestMessageBytes = 1024
- $0.maxResponseMessageBytes = 2048
- }
- // Test the 'regular' config.
- do {
- let jsonConfig = try config.jsonUTF8Data()
- let decoded = try self.decoder.decode(MethodConfig.self, from: jsonConfig)
- XCTAssertEqual(decoded.names, [MethodConfig.Name(service: "echo.Echo", method: "Get")])
- XCTAssertEqual(decoded.waitForReady, true)
- XCTAssertEqual(decoded.timeout, Duration(secondsComponent: 1, attosecondsComponent: 0))
- XCTAssertEqual(decoded.maxRequestMessageBytes, 1024)
- XCTAssertEqual(decoded.maxResponseMessageBytes, 2048)
- XCTAssertNil(decoded.executionPolicy)
- }
- // Test the hedging policy.
- do {
- var config = config
- config.hedgingPolicy = .with {
- $0.maxAttempts = 3
- $0.hedgingDelay = .with { $0.seconds = 42 }
- $0.nonFatalStatusCodes = [
- .aborted,
- .unimplemented,
- ]
- }
- let jsonConfig = try config.jsonUTF8Data()
- let decoded = try self.decoder.decode(MethodConfig.self, from: jsonConfig)
- switch decoded.executionPolicy?.wrapped {
- case let .some(.hedge(policy)):
- XCTAssertEqual(policy.maximumAttempts, 3)
- XCTAssertEqual(policy.hedgingDelay, .seconds(42))
- XCTAssertEqual(policy.nonFatalStatusCodes, [.aborted, .unimplemented])
- default:
- XCTFail("Expected hedging policy")
- }
- }
- // Test the retry policy.
- do {
- var config = config
- config.retryPolicy = .with {
- $0.maxAttempts = 3
- $0.initialBackoff = .with { $0.seconds = 1 }
- $0.maxBackoff = .with { $0.seconds = 3 }
- $0.backoffMultiplier = 1.6
- $0.retryableStatusCodes = [
- .aborted,
- .unimplemented,
- ]
- }
- let jsonConfig = try config.jsonUTF8Data()
- let decoded = try self.decoder.decode(MethodConfig.self, from: jsonConfig)
- switch decoded.executionPolicy?.wrapped {
- case let .some(.retry(policy)):
- XCTAssertEqual(policy.maximumAttempts, 3)
- XCTAssertEqual(policy.initialBackoff, .seconds(1))
- XCTAssertEqual(policy.maximumBackoff, .seconds(3))
- XCTAssertEqual(policy.backoffMultiplier, 1.6)
- XCTAssertEqual(policy.retryableStatusCodes, [.aborted, .unimplemented])
- default:
- XCTFail("Expected hedging policy")
- }
- }
- }
- }
|