diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 1b6418e..fda722e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -13,10 +13,10 @@ jobs: with: linux_5_9_enabled: false linux_5_10_enabled: false - linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" + linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" + linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" + linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" static-sdk: name: Static SDK diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml index ba463a1..c9852cf 100644 --- a/.github/workflows/pull_request.yml +++ b/.github/workflows/pull_request.yml @@ -25,10 +25,10 @@ jobs: with: linux_5_9_enabled: false linux_5_10_enabled: false - linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" - linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" + linux_6_1_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -warnings-as-errors -Xswiftc -require-explicit-availability" + linux_nightly_next_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" + linux_nightly_main_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-availability" cxx-interop: name: Cxx interop diff --git a/Package.swift b/Package.swift index e806232..073f237 100644 --- a/Package.swift +++ b/Package.swift @@ -47,7 +47,7 @@ let dependencies: [Package.Dependency] = [ ), .package( url: "https://github.com/grpc/grpc-swift-protobuf.git", - from: "1.0.0" + from: "1.3.0" ), .package( url: "https://github.com/apple/swift-protobuf.git", @@ -59,16 +59,32 @@ let dependencies: [Package.Dependency] = [ ), .package( url: "https://github.com/swift-server/swift-service-lifecycle.git", - from: "2.6.3" + from: "2.8.0" ), ] -let defaultSwiftSettings: [SwiftSetting] = [ - .swiftLanguageMode(.v6), - .enableUpcomingFeature("ExistentialAny"), - .enableUpcomingFeature("InternalImportsByDefault"), - .enableUpcomingFeature("MemberImportVisibility"), -] +// ------------------------------------------------------------------------------------------------- + +// This adds some build settings which allow us to map "@available(gRPCSwiftExtras 1.x, *)" to +// the appropriate OS platforms. +let nextMinorVersion = 1 +let availabilitySettings: [SwiftSetting] = (0 ... nextMinorVersion).map { minor in + let name = "gRPCSwiftExtras" + let version = "1.\(minor)" + let platforms = "macOS 15.0, iOS 18.0, watchOS 11.0, tvOS 18.0, visionOS 2.0" + let setting = "AvailabilityMacro=\(name) \(version):\(platforms)" + return .enableExperimentalFeature(setting) +} + +let defaultSwiftSettings: [SwiftSetting] = + availabilitySettings + [ + .swiftLanguageMode(.v6), + .enableUpcomingFeature("ExistentialAny"), + .enableUpcomingFeature("InternalImportsByDefault"), + .enableUpcomingFeature("MemberImportVisibility"), + ] + +// ------------------------------------------------------------------------------------------------- let targets: [Target] = [ // An implementation of the gRPC Health service. @@ -177,13 +193,6 @@ let targets: [Target] = [ let package = Package( name: "grpc-swift-extras", - platforms: [ - .macOS(.v15), - .iOS(.v18), - .tvOS(.v18), - .watchOS(.v11), - .visionOS(.v2), - ], products: products, dependencies: dependencies, targets: targets diff --git a/Sources/GRPCHealthService/HealthService+Service.swift b/Sources/GRPCHealthService/HealthService+Service.swift index 165d1c8..e4c31b3 100644 --- a/Sources/GRPCHealthService/HealthService+Service.swift +++ b/Sources/GRPCHealthService/HealthService+Service.swift @@ -17,12 +17,14 @@ internal import GRPCCore private import Synchronization +@available(gRPCSwiftExtras 1.0, *) extension HealthService { internal struct Service: Grpc_Health_V1_Health.ServiceProtocol { private let state = Self.State() } } +@available(gRPCSwiftExtras 1.0, *) extension HealthService.Service { func check( request: ServerRequest, @@ -69,6 +71,7 @@ extension HealthService.Service { } } +@available(gRPCSwiftExtras 1.0, *) extension HealthService.Service { private final class State: Sendable { // The state of each service keyed by the fully qualified service name. diff --git a/Sources/GRPCHealthService/HealthService.swift b/Sources/GRPCHealthService/HealthService.swift index 988c227..eab4b36 100644 --- a/Sources/GRPCHealthService/HealthService.swift +++ b/Sources/GRPCHealthService/HealthService.swift @@ -41,6 +41,7 @@ public import GRPCCore /// // ... /// } /// ``` +@available(gRPCSwiftExtras 1.0, *) public struct HealthService: Sendable, RegistrableRPCService { /// An implementation of the `grpc.health.v1.Health` service. private let service: Service @@ -62,6 +63,7 @@ public struct HealthService: Sendable, RegistrableRPCService { } } +@available(gRPCSwiftExtras 1.0, *) extension HealthService { /// Provides status updates to ``HealthService``. public struct Provider: Sendable { @@ -105,6 +107,7 @@ extension HealthService { } } +@available(gRPCSwiftExtras 1.0, *) extension Grpc_Health_V1_HealthCheckResponse.ServingStatus { package init(_ status: ServingStatus) { switch status.value { diff --git a/Sources/GRPCHealthService/ServingStatus.swift b/Sources/GRPCHealthService/ServingStatus.swift index cc0fd5b..20c62e4 100644 --- a/Sources/GRPCHealthService/ServingStatus.swift +++ b/Sources/GRPCHealthService/ServingStatus.swift @@ -18,6 +18,7 @@ /// /// - ``ServingStatus/serving`` indicates that a service is healthy. /// - ``ServingStatus/notServing`` indicates that a service is unhealthy. +@available(gRPCSwiftExtras 1.0, *) public struct ServingStatus: Sendable, Hashable { internal enum Value: Sendable, Hashable { case serving diff --git a/Sources/GRPCInteropTests/AssertionFailure.swift b/Sources/GRPCInteropTests/AssertionFailure.swift index 112a36e..9f20c37 100644 --- a/Sources/GRPCInteropTests/AssertionFailure.swift +++ b/Sources/GRPCInteropTests/AssertionFailure.swift @@ -17,6 +17,7 @@ /// Failure assertion for interoperability testing. /// /// This is required because the tests must be able to run without XCTest. +@available(gRPCSwiftExtras 1.0, *) public struct AssertionFailure: Error { public var message: String public var file: String @@ -30,6 +31,7 @@ public struct AssertionFailure: Error { } /// Asserts that the value of an expression is `true`. +@available(gRPCSwiftExtras 1.0, *) public func assertTrue( _ expression: @autoclosure () throws -> Bool, _ message: String = "The statement is not true.", @@ -42,6 +44,7 @@ public func assertTrue( } /// Asserts that the two given values are equal. +@available(gRPCSwiftExtras 1.0, *) public func assertEqual( _ value1: T, _ value2: T, diff --git a/Sources/GRPCInteropTests/InteroperabilityTestCase.swift b/Sources/GRPCInteropTests/InteroperabilityTestCase.swift index 07991eb..814a8a8 100644 --- a/Sources/GRPCInteropTests/InteroperabilityTestCase.swift +++ b/Sources/GRPCInteropTests/InteroperabilityTestCase.swift @@ -15,6 +15,7 @@ */ public import GRPCCore +@available(gRPCSwiftExtras 1.0, *) public protocol InteroperabilityTest { /// Run a test case using the given connection. /// @@ -47,6 +48,7 @@ public protocol InteroperabilityTest { /// Note: Tests for compression have not been implemented yet as compression is /// not supported. Once the API which allows for compression will be implemented /// these tests should be added. +@available(gRPCSwiftExtras 1.0, *) public enum InteroperabilityTestCase: String, CaseIterable, Sendable { case emptyUnary = "empty_unary" case largeUnary = "large_unary" @@ -68,6 +70,7 @@ public enum InteroperabilityTestCase: String, CaseIterable, Sendable { } } +@available(gRPCSwiftExtras 1.0, *) extension InteroperabilityTestCase { /// Return a new instance of the test case. public func makeTest() -> any InteroperabilityTest { diff --git a/Sources/GRPCInteropTests/InteroperabilityTestCases.swift b/Sources/GRPCInteropTests/InteroperabilityTestCases.swift index 4802763..b583da6 100644 --- a/Sources/GRPCInteropTests/InteroperabilityTestCases.swift +++ b/Sources/GRPCInteropTests/InteroperabilityTestCases.swift @@ -36,6 +36,7 @@ private import struct Foundation.Data /// Client asserts: /// - call was successful /// - response is non-null +@available(gRPCSwiftExtras 1.0, *) struct EmptyUnary: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -69,6 +70,7 @@ struct EmptyUnary: InteroperabilityTest { /// - response payload body is 314159 bytes in size /// - clients are free to assert that the response payload body contents are zero and comparing /// the entire response message against a golden response +@available(gRPCSwiftExtras 1.0, *) struct LargeUnary: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -144,6 +146,7 @@ struct LargeUnary: InteroperabilityTest { /// - Response payload body is 314159 bytes in size. /// - Clients are free to assert that the response payload body contents are zeros and comparing the /// entire response message against a golden response. +@available(gRPCSwiftExtras 1.0, *) class ClientCompressedUnary: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -253,6 +256,7 @@ class ClientCompressedUnary: InteroperabilityTest { /// - response payload body is 314159 bytes in size in both cases. /// - clients are free to assert that the response payload body contents are zero and comparing the /// entire response message against a golden response +@available(gRPCSwiftExtras 1.0, *) class ServerCompressedUnary: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -342,6 +346,7 @@ class ServerCompressedUnary: InteroperabilityTest { /// Client asserts: /// - call was successful /// - response aggregated_payload_size is 74922 +@available(gRPCSwiftExtras 1.0, *) struct ClientStreaming: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -392,6 +397,7 @@ struct ClientStreaming: InteroperabilityTest { /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979 /// - clients are free to assert that the response payload body contents are zero and /// comparing the entire response messages against golden responses +@available(gRPCSwiftExtras 1.0, *) struct ServerStreaming: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -467,6 +473,7 @@ struct ServerStreaming: InteroperabilityTest { /// - response payload bodies are sized (in order): 31415, 92653 /// - clients are free to assert that the response payload body contents are zero and comparing the /// entire response messages against golden responses +@available(gRPCSwiftExtras 1.0, *) class ServerCompressedStreaming: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -579,6 +586,7 @@ class ServerCompressedStreaming: InteroperabilityTest { /// - response payload bodies are sized (in order): 31415, 9, 2653, 58979 /// - clients are free to assert that the response payload body contents are zero and /// comparing the entire response messages against golden responses +@available(gRPCSwiftExtras 1.0, *) struct PingPong: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -645,6 +653,7 @@ struct PingPong: InteroperabilityTest { /// Client asserts: /// - call was successful /// - exactly zero responses +@available(gRPCSwiftExtras 1.0, *) struct EmptyStream: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -700,6 +709,7 @@ struct EmptyStream: InteroperabilityTest { /// received in the initial metadata for calls in Procedure steps 1 and 2. /// - metadata with key "x-grpc-test-echo-trailing-bin" and value 0xababab is received in the /// trailing metadata for calls in Procedure steps 1 and 2. +@available(gRPCSwiftExtras 1.0, *) struct CustomMetadata: InteroperabilityTest { let initialMetadataName = "x-grpc-test-echo-initial" let initialMetadataValue = "test_initial_metadata_value" @@ -820,6 +830,7 @@ struct CustomMetadata: InteroperabilityTest { /// Client asserts: /// - received status code is the same as the sent code for both Procedure steps 1 and 2 /// - received status message is the same as the sent message for both Procedure steps 1 and 2 +@available(gRPCSwiftExtras 1.0, *) struct StatusCodeAndMessage: InteroperabilityTest { let expectedCode = 2 let expectedMessage = "test status message" @@ -897,6 +908,7 @@ struct StatusCodeAndMessage: InteroperabilityTest { /// - received status code is the same as the sent code for Procedure step 1 /// - received status message is the same as the sent message for Procedure step 1, including all /// whitespace characters +@available(gRPCSwiftExtras 1.0, *) struct SpecialStatusMessage: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -939,6 +951,7 @@ struct SpecialStatusMessage: InteroperabilityTest { /// /// Client asserts: /// - received status code is 12 (UNIMPLEMENTED) +@available(gRPCSwiftExtras 1.0, *) struct UnimplementedMethod: InteroperabilityTest { func run(client: GRPCClient) async throws { let testServiceClient = Grpc_Testing_TestService.Client(wrapping: client) @@ -971,6 +984,7 @@ struct UnimplementedMethod: InteroperabilityTest { /// /// Client asserts: /// - received status code is 12 (UNIMPLEMENTED) +@available(gRPCSwiftExtras 1.0, *) struct UnimplementedService: InteroperabilityTest { func run(client: GRPCClient) async throws { let unimplementedServiceClient = Grpc_Testing_UnimplementedService.Client(wrapping: client) diff --git a/Sources/GRPCInteropTests/TestService.swift b/Sources/GRPCInteropTests/TestService.swift index ef2d7cc..1fa20f2 100644 --- a/Sources/GRPCInteropTests/TestService.swift +++ b/Sources/GRPCInteropTests/TestService.swift @@ -23,6 +23,7 @@ private import struct FoundationEssentials.Data private import struct Foundation.Data #endif +@available(gRPCSwiftExtras 1.0, *) public struct TestService: Grpc_Testing_TestService.ServiceProtocol { public init() {} @@ -233,6 +234,7 @@ public struct TestService: Grpc_Testing_TestService.ServiceProtocol { } } +@available(gRPCSwiftExtras 1.0, *) extension Metadata { fileprivate func makeInitialAndTrailingMetadata() -> (Metadata, Metadata) { var initialMetadata = Metadata() diff --git a/Sources/GRPCOTelTracingInterceptors/HookedAsyncSequence.swift b/Sources/GRPCOTelTracingInterceptors/HookedAsyncSequence.swift index fdbe100..c048963 100644 --- a/Sources/GRPCOTelTracingInterceptors/HookedAsyncSequence.swift +++ b/Sources/GRPCOTelTracingInterceptors/HookedAsyncSequence.swift @@ -14,6 +14,7 @@ * limitations under the License. */ +@available(gRPCSwiftExtras 1.0, *) internal struct HookedRPCAsyncSequence: AsyncSequence, Sendable where Wrapped.Element: Sendable { private let wrapped: Wrapped diff --git a/Sources/GRPCOTelTracingInterceptors/HookedWriter.swift b/Sources/GRPCOTelTracingInterceptors/HookedWriter.swift index 1baec5b..8b1f39e 100644 --- a/Sources/GRPCOTelTracingInterceptors/HookedWriter.swift +++ b/Sources/GRPCOTelTracingInterceptors/HookedWriter.swift @@ -16,6 +16,7 @@ internal import GRPCCore internal import Tracing +@available(gRPCSwiftExtras 1.0, *) struct HookedWriter: RPCWriterProtocol { private let writer: any RPCWriterProtocol private let afterEachWrite: @Sendable () -> Void diff --git a/Sources/GRPCOTelTracingInterceptors/Tracing/ClientOTelTracingInterceptor.swift b/Sources/GRPCOTelTracingInterceptors/Tracing/ClientOTelTracingInterceptor.swift index e379b31..4be57ed 100644 --- a/Sources/GRPCOTelTracingInterceptors/Tracing/ClientOTelTracingInterceptor.swift +++ b/Sources/GRPCOTelTracingInterceptors/Tracing/ClientOTelTracingInterceptor.swift @@ -29,6 +29,7 @@ package import Tracing /// OpenTelemetry's documentation on: /// - https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans /// - https://opentelemetry.io/docs/specs/semconv/rpc/grpc/ +@available(gRPCSwiftExtras 1.0, *) public struct ClientOTelTracingInterceptor: ClientInterceptor { private let injector: ClientRequestInjector private var serverHostname: String @@ -241,6 +242,7 @@ public struct ClientOTelTracingInterceptor: ClientInterceptor { /// An injector responsible for injecting the required instrumentation keys from the `ServiceContext` into /// the request metadata. +@available(gRPCSwiftExtras 1.0, *) struct ClientRequestInjector: Instrumentation.Injector { typealias Carrier = Metadata @@ -249,6 +251,7 @@ struct ClientRequestInjector: Instrumentation.Injector { } } +@available(gRPCSwiftExtras 1.0, *) extension Error { var grpcErrorCode: RPCError.Code? { if let rpcError = self as? RPCError { diff --git a/Sources/GRPCOTelTracingInterceptors/Tracing/ServerOTelTracingInterceptor.swift b/Sources/GRPCOTelTracingInterceptors/Tracing/ServerOTelTracingInterceptor.swift index 586b12d..3c66edb 100644 --- a/Sources/GRPCOTelTracingInterceptors/Tracing/ServerOTelTracingInterceptor.swift +++ b/Sources/GRPCOTelTracingInterceptors/Tracing/ServerOTelTracingInterceptor.swift @@ -28,6 +28,7 @@ package import Tracing /// OpenTelemetry's documentation on: /// - https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans /// - https://opentelemetry.io/docs/specs/semconv/rpc/grpc/ +@available(gRPCSwiftExtras 1.0, *) public struct ServerOTelTracingInterceptor: ServerInterceptor { private let extractor: ServerRequestExtractor private var serverHostname: String @@ -217,6 +218,7 @@ public struct ServerOTelTracingInterceptor: ServerInterceptor { } /// An extractor responsible for extracting the required instrumentation keys from request metadata. +@available(gRPCSwiftExtras 1.0, *) struct ServerRequestExtractor: Instrumentation.Extractor { typealias Carrier = Metadata diff --git a/Sources/GRPCOTelTracingInterceptors/Tracing/SpanAttributes+GRPCTracingKeys.swift b/Sources/GRPCOTelTracingInterceptors/Tracing/SpanAttributes+GRPCTracingKeys.swift index 503ccb9..2c196db 100644 --- a/Sources/GRPCOTelTracingInterceptors/Tracing/SpanAttributes+GRPCTracingKeys.swift +++ b/Sources/GRPCOTelTracingInterceptors/Tracing/SpanAttributes+GRPCTracingKeys.swift @@ -40,6 +40,7 @@ enum GRPCTracingKeys { fileprivate static let responseMetadataPrefix = "rpc.grpc.response.metadata." } +@available(gRPCSwiftExtras 1.0, *) extension Span { // See: https://opentelemetry.io/docs/specs/semconv/rpc/rpc-spans/ func setOTelClientSpanGRPCAttributes( diff --git a/Sources/GRPCReflectionService/Service/ReflectionService+V1.swift b/Sources/GRPCReflectionService/Service/ReflectionService+V1.swift index 78b72bc..54113eb 100644 --- a/Sources/GRPCReflectionService/Service/ReflectionService+V1.swift +++ b/Sources/GRPCReflectionService/Service/ReflectionService+V1.swift @@ -17,6 +17,7 @@ internal import GRPCCore internal import SwiftProtobuf +@available(gRPCSwiftExtras 1.0, *) extension ReflectionService { struct V1: Grpc_Reflection_V1_ServerReflection.SimpleServiceProtocol { private typealias Response = Grpc_Reflection_V1_ServerReflectionResponse @@ -31,6 +32,7 @@ extension ReflectionService { } } +@available(gRPCSwiftExtras 1.0, *) extension ReflectionService.V1 { private func findFileByFileName(_ fileName: String) throws(RPCError) -> FileDescriptorResponse { let data = try self.registry.serialisedFileDescriptorForDependenciesOfFile(named: fileName) diff --git a/Sources/GRPCReflectionService/Service/ReflectionService.swift b/Sources/GRPCReflectionService/Service/ReflectionService.swift index 7abdadc..3647be2 100644 --- a/Sources/GRPCReflectionService/Service/ReflectionService.swift +++ b/Sources/GRPCReflectionService/Service/ReflectionService.swift @@ -33,6 +33,7 @@ public import struct Foundation.Data /// /// The service will offer information to clients about any registered services. You can register /// a service by providing its descriptor set to the service. +@available(gRPCSwiftExtras 1.0, *) public struct ReflectionService: Sendable { private let service: ReflectionService.V1 @@ -67,6 +68,7 @@ public struct ReflectionService: Sendable { } } +@available(gRPCSwiftExtras 1.0, *) extension ReflectionService: RegistrableRPCService { public func registerMethods( with router: inout RPCRouter @@ -75,6 +77,7 @@ extension ReflectionService: RegistrableRPCService { } } +@available(gRPCSwiftExtras 1.0, *) extension ReflectionService { static func readSerializedFileDescriptorProto( atPath path: String diff --git a/Sources/GRPCReflectionService/Service/ReflectionServiceRegistry.swift b/Sources/GRPCReflectionService/Service/ReflectionServiceRegistry.swift index ca42635..55ed34e 100644 --- a/Sources/GRPCReflectionService/Service/ReflectionServiceRegistry.swift +++ b/Sources/GRPCReflectionService/Service/ReflectionServiceRegistry.swift @@ -24,6 +24,7 @@ package import struct FoundationEssentials.Data package import struct Foundation.Data #endif +@available(gRPCSwiftExtras 1.0, *) package struct ReflectionServiceRegistry: Sendable { private struct SerializedFileDescriptor: Sendable { var bytes: Data diff --git a/Sources/GRPCServiceLifecycle/GRPCClient+Service.swift b/Sources/GRPCServiceLifecycle/GRPCClient+Service.swift index b601e59..52f711c 100644 --- a/Sources/GRPCServiceLifecycle/GRPCClient+Service.swift +++ b/Sources/GRPCServiceLifecycle/GRPCClient+Service.swift @@ -19,6 +19,7 @@ public import ServiceLifecycle // A `@retroactive` conformance here is okay because this project is also owned by the owners of // `GRPCCore`, and thus, the owners of `GRPCClient`. A conflicting conformance won't be added. +@available(gRPCSwiftExtras 1.0, *) extension GRPCClient: @retroactive Service { public func run() async throws { try await withGracefulShutdownHandler { diff --git a/Sources/GRPCServiceLifecycle/GRPCServer+Service.swift b/Sources/GRPCServiceLifecycle/GRPCServer+Service.swift index 95acf02..33fa499 100644 --- a/Sources/GRPCServiceLifecycle/GRPCServer+Service.swift +++ b/Sources/GRPCServiceLifecycle/GRPCServer+Service.swift @@ -19,6 +19,7 @@ public import ServiceLifecycle // A `@retroactive` conformance here is okay because this project is also owned by the owners of // `GRPCCore`, and thus, the owners of `GRPCServer`. A conflicting conformance won't be added. +@available(gRPCSwiftExtras 1.0, *) extension GRPCServer: @retroactive Service { public func run() async throws { try await withGracefulShutdownHandler { diff --git a/Tests/GRPCHealthServiceTests/HealthTests.swift b/Tests/GRPCHealthServiceTests/HealthTests.swift index 7f53656..e51bafd 100644 --- a/Tests/GRPCHealthServiceTests/HealthTests.swift +++ b/Tests/GRPCHealthServiceTests/HealthTests.swift @@ -20,6 +20,7 @@ import GRPCInProcessTransport import SwiftProtobuf import XCTest +@available(gRPCSwiftExtras 1.0, *) final class HealthTests: XCTestCase { private func withHealthClient( _ body: @Sendable ( @@ -286,6 +287,7 @@ final class HealthTests: XCTestCase { } } +@available(gRPCSwiftExtras 1.0, *) extension ServiceDescriptor { fileprivate static let testService = ServiceDescriptor(package: "test", service: "Service") } diff --git a/Tests/GRPCOTelTracingInterceptorsTests/GRPCOTelTracingInterceptorsTests.swift b/Tests/GRPCOTelTracingInterceptorsTests/GRPCOTelTracingInterceptorsTests.swift index 8085635..d20e93d 100644 --- a/Tests/GRPCOTelTracingInterceptorsTests/GRPCOTelTracingInterceptorsTests.swift +++ b/Tests/GRPCOTelTracingInterceptorsTests/GRPCOTelTracingInterceptorsTests.swift @@ -23,17 +23,13 @@ import struct Foundation.UUID @Suite("OTel Tracing Client Interceptor Tests") struct OTelTracingClientInterceptorTests { - private let tracer: TestTracer - - init() { - self.tracer = TestTracer() - } - @Test( "Successful RPC is recorded correctly", arguments: OTelTracingInterceptorTestAddressType.allCases ) + @available(gRPCSwiftExtras 1.0, *) func testSuccessfulRPC(addressType: OTelTracingInterceptorTestAddressType) async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString let (requestStream, requestStreamContinuation) = AsyncStream.makeStream() @@ -57,7 +53,7 @@ struct OTelTracingClientInterceptorTests { methodDescriptor: methodDescriptor ) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(producer: { writer in try await writer.write(contentsOf: ["request1"]) try await writer.write(contentsOf: ["request2"]) @@ -90,7 +86,7 @@ struct OTelTracingClientInterceptorTests { await assertStreamContentsEqual(["request1", "request2"], requestStream) try await assertStreamContentsEqual([["response"]], response.messages) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in // No events are recorded #expect(events.isEmpty) } assertAttributes: { attributes in @@ -104,7 +100,9 @@ struct OTelTracingClientInterceptorTests { } @Test("All events are recorded when traceEachMessage is true") + @available(gRPCSwiftExtras 1.0, *) func testAllEventsRecorded() async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString @@ -126,7 +124,7 @@ struct OTelTracingClientInterceptorTests { ) let testValues = self.getTestValues(addressType: .ipv4, methodDescriptor: methodDescriptor) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(producer: { writer in try await writer.write(contentsOf: ["request1"]) try await writer.write(contentsOf: ["request2"]) @@ -159,7 +157,7 @@ struct OTelTracingClientInterceptorTests { await assertStreamContentsEqual(["request1", "request2"], requestStream) try await assertStreamContentsEqual([["response"]], response.messages) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect( events == [ // Recorded when `request1` is sent @@ -181,7 +179,9 @@ struct OTelTracingClientInterceptorTests { } @Test("All string-valued request metadata is included if opted-in") + @available(gRPCSwiftExtras 1.0, *) func testRequestMetadataOptIn() async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString @@ -202,7 +202,7 @@ struct OTelTracingClientInterceptorTests { method: "testRequestMetadataOptIn" ) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init( metadata: [ "some-request-metadata": "some-request-value", @@ -256,7 +256,7 @@ struct OTelTracingClientInterceptorTests { await assertStreamContentsEqual(["request1", "request2"], requestStream) try await assertStreamContentsEqual([["response"]], response.messages) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect( events == [ // Recorded when `request1` is sent @@ -295,7 +295,9 @@ struct OTelTracingClientInterceptorTests { } @Test("All string-valued response metadata is included if opted-in") + @available(gRPCSwiftExtras 1.0, *) func testResponseMetadataOptIn() async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString @@ -316,7 +318,7 @@ struct OTelTracingClientInterceptorTests { method: "testResponseMetadataOptIn" ) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init( metadata: [ "some-request-metadata": "some-request-value", @@ -370,7 +372,7 @@ struct OTelTracingClientInterceptorTests { await assertStreamContentsEqual(["request1", "request2"], requestStream) try await assertStreamContentsEqual([["response"]], response.messages) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect( events == [ // Recorded when `request1` is sent @@ -409,7 +411,9 @@ struct OTelTracingClientInterceptorTests { } @Test("RPC that throws is correctly recorded") + @available(gRPCSwiftExtras 1.0, *) func testThrowingRPC() async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString serviceContext.traceID = traceIDString @@ -429,7 +433,7 @@ struct OTelTracingClientInterceptorTests { ) do { let _: StreamingClientResponse = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: StreamingClientRequest(of: Void.self, producer: { writer in }), context: ClientContext( descriptor: methodDescriptor, @@ -444,7 +448,7 @@ struct OTelTracingClientInterceptorTests { } Issue.record("Should have thrown") } catch { - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in // No events are recorded #expect(events.isEmpty) } assertAttributes: { attributes in @@ -472,7 +476,9 @@ struct OTelTracingClientInterceptorTests { } @Test("RPC with a failure response is correctly recorded") + @available(gRPCSwiftExtras 1.0, *) func testFailedRPC() async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString let (requestStream, requestStreamContinuation) = AsyncStream.makeStream() @@ -492,7 +498,7 @@ struct OTelTracingClientInterceptorTests { method: "testFailedRPC" ) let response: StreamingClientResponse = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(producer: { writer in try await writer.write(contentsOf: ["request"]) }), @@ -524,7 +530,7 @@ struct OTelTracingClientInterceptorTests { #expect(failure == RPCError(code: .unavailable, message: "This should not work")) } - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in // No events are recorded #expect(events.isEmpty) } assertAttributes: { attributes in @@ -551,7 +557,9 @@ struct OTelTracingClientInterceptorTests { } @Test("Accepted server-streaming RPC that throws error during response is correctly recorded") + @available(gRPCSwiftExtras 1.0, *) func testAcceptedRPCWithError() async throws { + let tracer = TestTracer() var serviceContext = ServiceContext.topLevel let traceIDString = UUID().uuidString serviceContext.traceID = traceIDString @@ -570,7 +578,7 @@ struct OTelTracingClientInterceptorTests { method: "testAcceptedRPCWithError" ) let response: StreamingClientResponse = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(producer: { writer in try await writer.write(contentsOf: ["request"]) }), @@ -614,7 +622,7 @@ struct OTelTracingClientInterceptorTests { return } - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in // No events are recorded #expect(events.isEmpty) } assertAttributes: { attributes in @@ -640,6 +648,7 @@ struct OTelTracingClientInterceptorTests { } } + @available(gRPCSwiftExtras 1.0, *) private func getTestValues( addressType: OTelTracingInterceptorTestAddressType, methodDescriptor: MethodDescriptor @@ -701,17 +710,13 @@ struct OTelTracingClientInterceptorTests { @Suite("OTel Tracing Server Interceptor Tests") struct OTelTracingServerInterceptorTests { - private let tracer: TestTracer - - init() { - self.tracer = TestTracer() - } - @Test( "Successful RPC is recorded correctly", arguments: OTelTracingInterceptorTestAddressType.allCases ) + @available(gRPCSwiftExtras 1.0, *) func testSuccessfulRPC(addressType: OTelTracingInterceptorTestAddressType) async throws { + let tracer = TestTracer() let methodDescriptor = MethodDescriptor( fullyQualifiedService: "OTelTracingServerInterceptorTests", method: "testSuccessfulRPC" @@ -729,7 +734,7 @@ struct OTelTracingServerInterceptorTests { methodDescriptor: methodDescriptor ) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(single: request), context: ServerContext( descriptor: methodDescriptor, @@ -766,7 +771,7 @@ struct OTelTracingServerInterceptorTests { await assertStreamContentsEqual(["response1", "response2"], responseStream) #expect(trailingMetadata == ["Result": "Trailing metadata"]) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect(events.isEmpty) } assertAttributes: { attributes in #expect(attributes == testValues.expectedSpanAttributes) @@ -778,7 +783,9 @@ struct OTelTracingServerInterceptorTests { } @Test("All events are recorded when traceEachMessage is true") + @available(gRPCSwiftExtras 1.0, *) func testAllEventsRecorded() async throws { + let tracer = TestTracer() let methodDescriptor = MethodDescriptor( fullyQualifiedService: "OTelTracingServerInterceptorTests", method: "testAllEventsRecorded" @@ -792,7 +799,7 @@ struct OTelTracingServerInterceptorTests { let request = ServerRequest(metadata: ["trace-id": .string(traceIDString)], message: [UInt8]()) let testValues = getTestValues(addressType: .ipv4, methodDescriptor: methodDescriptor) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(single: request), context: ServerContext( descriptor: methodDescriptor, @@ -833,7 +840,7 @@ struct OTelTracingServerInterceptorTests { #expect(trailingMetadata == ["Result": "Trailing metadata"]) await assertStreamContentsEqual(["response1", "response2"], responseStream) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect( events == [ // Recorded when request is received @@ -854,7 +861,9 @@ struct OTelTracingServerInterceptorTests { } @Test("All string-valued request metadata is included if opted-in") + @available(gRPCSwiftExtras 1.0, *) func testRequestMetadataOptIn() async throws { + let tracer = TestTracer() let methodDescriptor = MethodDescriptor( fullyQualifiedService: "OTelTracingServerInterceptorTests", method: "testRequestMetadataOptIn" @@ -874,7 +883,7 @@ struct OTelTracingServerInterceptorTests { message: [UInt8]() ) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(single: request), context: ServerContext( descriptor: methodDescriptor, @@ -923,7 +932,7 @@ struct OTelTracingServerInterceptorTests { ) await assertStreamContentsEqual(["response1", "response2"], responseStream) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect( events == [ // Recorded when request is received @@ -962,7 +971,9 @@ struct OTelTracingServerInterceptorTests { } @Test("All string-valued response metadata is included if opted-in") + @available(gRPCSwiftExtras 1.0, *) func testResponseMetadataOptIn() async throws { + let tracer = TestTracer() let methodDescriptor = MethodDescriptor( fullyQualifiedService: "OTelTracingServerInterceptorTests", method: "testResponseMetadataOptIn" @@ -982,7 +993,7 @@ struct OTelTracingServerInterceptorTests { message: [UInt8]() ) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(single: request), context: ServerContext( descriptor: methodDescriptor, @@ -1031,7 +1042,7 @@ struct OTelTracingServerInterceptorTests { ) await assertStreamContentsEqual(["response1", "response2"], responseStream) - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect( events == [ // Recorded when request is received @@ -1070,7 +1081,9 @@ struct OTelTracingServerInterceptorTests { } @Test("RPC that throws is correctly recorded") + @available(gRPCSwiftExtras 1.0, *) func testThrowingRPC() async throws { + let tracer = TestTracer() let methodDescriptor = MethodDescriptor( fullyQualifiedService: "TracingInterceptorTests", method: "testServerInterceptorErrorEncountered" @@ -1085,7 +1098,7 @@ struct OTelTracingServerInterceptorTests { let testValues = getTestValues(addressType: .ipv4, methodDescriptor: methodDescriptor) do { let _: StreamingServerResponse = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(single: request), context: ServerContext( descriptor: methodDescriptor, @@ -1101,7 +1114,7 @@ struct OTelTracingServerInterceptorTests { } Issue.record("Should have thrown") } catch { - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect(events.isEmpty) } assertAttributes: { attributes in // The attributes should not contain a grpc status code, as the request was never even sent. @@ -1115,7 +1128,9 @@ struct OTelTracingServerInterceptorTests { } @Test("RPC with a failure response is correctly recorded") + @available(gRPCSwiftExtras 1.0, *) func testFailedRPC() async throws { + let tracer = TestTracer() let methodDescriptor = MethodDescriptor( fullyQualifiedService: "TracingInterceptorTests", method: "testServerInterceptorErrorResponse" @@ -1129,7 +1144,7 @@ struct OTelTracingServerInterceptorTests { let request = ServerRequest(metadata: ["trace-id": .string(traceIDString)], message: [UInt8]()) let testValues = getTestValues(addressType: .ipv4, methodDescriptor: methodDescriptor) let response = try await interceptor.intercept( - tracer: self.tracer, + tracer: tracer, request: .init(single: request), context: ServerContext( descriptor: methodDescriptor, @@ -1150,7 +1165,7 @@ struct OTelTracingServerInterceptorTests { try response.accepted.get() } - assertTestSpanComponents(forMethod: methodDescriptor, tracer: self.tracer) { events in + assertTestSpanComponents(forMethod: methodDescriptor, tracer: tracer) { events in #expect(events.isEmpty) } assertAttributes: { attributes in #expect( @@ -1176,6 +1191,7 @@ struct OTelTracingServerInterceptorTests { } } + @available(gRPCSwiftExtras 1.0, *) private func getTestValues( addressType: OTelTracingInterceptorTestAddressType, methodDescriptor: MethodDescriptor @@ -1239,6 +1255,7 @@ struct OTelTracingServerInterceptorTests { // - MARK: Utilities +@available(gRPCSwiftExtras 1.0, *) private func getTestSpanForMethod( tracer: TestTracer, methodDescriptor: MethodDescriptor @@ -1246,6 +1263,7 @@ private func getTestSpanForMethod( tracer.getSpan(ofOperation: methodDescriptor.fullyQualifiedMethod)! } +@available(gRPCSwiftExtras 1.0, *) private func assertTestSpanComponents( forMethod method: MethodDescriptor, tracer: TestTracer, @@ -1261,6 +1279,7 @@ private func assertTestSpanComponents( assertErrors(span.errors) } +@available(gRPCSwiftExtras 1.0, *) private func assertStreamContentsEqual( _ array: [T], _ stream: any AsyncSequence @@ -1272,6 +1291,7 @@ private func assertStreamContentsEqual( #expect(streamElements == array) } +@available(gRPCSwiftExtras 1.0, *) private func assertStreamContentsEqual( _ array: [T], _ stream: any AsyncSequence diff --git a/Tests/GRPCOTelTracingInterceptorsTests/TracingTestsUtilities.swift b/Tests/GRPCOTelTracingInterceptorsTests/TracingTestsUtilities.swift index cecde4c..4344fe2 100644 --- a/Tests/GRPCOTelTracingInterceptorsTests/TracingTestsUtilities.swift +++ b/Tests/GRPCOTelTracingInterceptorsTests/TracingTestsUtilities.swift @@ -18,6 +18,7 @@ import GRPCCore import Synchronization import Tracing +@available(gRPCSwiftExtras 1.0, *) final class TestTracer: Tracer { typealias Span = TestSpan @@ -71,6 +72,7 @@ final class TestTracer: Tracer { } } +@available(gRPCSwiftExtras 1.0, *) final class TestSpan: Span, Sendable { private struct State { var context: ServiceContextModule.ServiceContext diff --git a/Tests/GRPCReflectionServiceTests/GRPCReflectionServiceTests.swift b/Tests/GRPCReflectionServiceTests/GRPCReflectionServiceTests.swift index 4c5499b..ba159b4 100644 --- a/Tests/GRPCReflectionServiceTests/GRPCReflectionServiceTests.swift +++ b/Tests/GRPCReflectionServiceTests/GRPCReflectionServiceTests.swift @@ -23,6 +23,7 @@ import Testing @Suite("gRPC Reflection Service Tests") struct GRPCReflectionServiceTests { + @available(gRPCSwiftExtras 1.0, *) func withReflectionClient( descriptorSetPaths: [String] = Bundle.module.pathsForDescriptorSets, execute body: (ReflectionClient) async throws -> Void @@ -39,6 +40,7 @@ struct GRPCReflectionServiceTests { } @Test("List services") + @available(gRPCSwiftExtras 1.0, *) func listServices() async throws { try await self.withReflectionClient { reflection in let services = try await reflection.listServices() @@ -57,6 +59,7 @@ struct GRPCReflectionServiceTests { "grpc/health/v1/health.proto", ] ) + @available(gRPCSwiftExtras 1.0, *) func fileByFileName(fileName: String) async throws { try await self.withReflectionClient { reflection in let descriptors = try await reflection.fileByFileName(fileName) @@ -66,6 +69,7 @@ struct GRPCReflectionServiceTests { } @Test("File by file name (doesn't exist)") + @available(gRPCSwiftExtras 1.0, *) func testFileByNonExistentFileName() async throws { try await self.withReflectionClient { reflection in await #expect { @@ -89,6 +93,7 @@ struct GRPCReflectionServiceTests { ("grpc/reflection/v1/reflection.proto", "grpc.reflection.v1.ErrorResponse"), ] as [(String, String)] ) + @available(gRPCSwiftExtras 1.0, *) func fileContainingSymbol(fileName: String, symbol: String) async throws { try await self.withReflectionClient { reflection in let descriptors = try await reflection.fileContainingSymbol(symbol) @@ -98,6 +103,7 @@ struct GRPCReflectionServiceTests { } @Test("File containing symbol includes dependencies") + @available(gRPCSwiftExtras 1.0, *) func fileContainingSymbolWithDependency() async throws { try await self.withReflectionClient { reflection in let descriptors = try await reflection.fileContainingSymbol(".MessageWithDependency") @@ -107,6 +113,7 @@ struct GRPCReflectionServiceTests { } @Test("File containing symbol (doesn't exist)") + @available(gRPCSwiftExtras 1.0, *) func testFileContainingNonExistentSymbol() async throws { try await self.withReflectionClient { reflection in await #expect { @@ -121,6 +128,7 @@ struct GRPCReflectionServiceTests { } @Test("File containing extension") + @available(gRPCSwiftExtras 1.0, *) func testFileContainingExtension() async throws { try await self.withReflectionClient { reflection in let descriptors = try await reflection.fileContainingExtension(number: 100, in: "BaseMessage") @@ -130,6 +138,7 @@ struct GRPCReflectionServiceTests { } @Test("File containing extension (doesn't exist)") + @available(gRPCSwiftExtras 1.0, *) func testFileContainingNonExistentExtension() async throws { try await self.withReflectionClient { reflection in await #expect { diff --git a/Tests/GRPCReflectionServiceTests/ReflectionClient.swift b/Tests/GRPCReflectionServiceTests/ReflectionClient.swift index d5be6ec..017289d 100644 --- a/Tests/GRPCReflectionServiceTests/ReflectionClient.swift +++ b/Tests/GRPCReflectionServiceTests/ReflectionClient.swift @@ -19,6 +19,7 @@ import GRPCInProcessTransport import GRPCReflectionService import SwiftProtobuf +@available(gRPCSwiftExtras 1.0, *) struct ReflectionClient: Sendable { private typealias Request = Grpc_Reflection_V1_ServerReflectionRequest private let stub: Grpc_Reflection_V1_ServerReflection.Client diff --git a/Tests/GRPCServiceLifecycleTests/ServiceLifecycleConformanceTests.swift b/Tests/GRPCServiceLifecycleTests/ServiceLifecycleConformanceTests.swift index c509cb1..bd4941b 100644 --- a/Tests/GRPCServiceLifecycleTests/ServiceLifecycleConformanceTests.swift +++ b/Tests/GRPCServiceLifecycleTests/ServiceLifecycleConformanceTests.swift @@ -23,6 +23,7 @@ import Testing @Suite("gRPC ServiceLifecycle/Service conformance tests") struct ServiceLifecycleConformanceTests { @Test("Client respects graceful shutdown") + @available(gRPCSwiftExtras 1.0, *) func clientGracefulShutdown() async throws { let inProcess = InProcessTransport() try await testGracefulShutdown { trigger in @@ -41,6 +42,7 @@ struct ServiceLifecycleConformanceTests { } @Test("Server respects graceful shutdown") + @available(gRPCSwiftExtras 1.0, *) func serverGracefulShutdown() async throws { let inProcess = InProcessTransport() try await testGracefulShutdown { trigger in diff --git a/Tests/InProcessInteropTests/InProcessInteroperabilityTests.swift b/Tests/InProcessInteropTests/InProcessInteroperabilityTests.swift index 685b243..2df207c 100644 --- a/Tests/InProcessInteropTests/InProcessInteroperabilityTests.swift +++ b/Tests/InProcessInteropTests/InProcessInteroperabilityTests.swift @@ -19,6 +19,7 @@ import GRPCInProcessTransport import GRPCInteropTests import XCTest +@available(gRPCSwiftExtras 1.0, *) final class InProcessInteroperabilityTests: XCTestCase { func runInProcessTransport( interopTestCase: InteroperabilityTestCase