From 94a6b2c4b1c93e5b24e9969c9746029876ea9268 Mon Sep 17 00:00:00 2001 From: Honza Dvorsky Date: Fri, 8 Nov 2024 17:02:03 +0100 Subject: [PATCH] [CI] Migrate to GitHub Actions (#57) --- .github/workflows/main.yml | 18 ++ .github/workflows/pull_request.yml | 26 ++ .github/workflows/pull_request_label.yml | 18 ++ .licenseignore | 9 + .swift-format | 61 ++++ .swiftformat | 20 -- CONTRIBUTING.md | 36 ++- CONTRIBUTORS.txt | 19 -- Package.swift | 17 +- README.md | 2 +- .../FilterInstanceServiceDiscovery.swift | 59 +++- Sources/ServiceDiscovery/HostPort.swift | 4 +- .../InMemoryServiceDiscovery.swift | 64 ++-- .../MapInstanceServiceDiscovery.swift | 56 +++- .../MapServiceServiceDiscovery.swift | 59 +++- .../ServiceDiscovery+AsyncAwait.swift | 103 +++--- .../ServiceDiscovery+Combinators.swift | 6 +- .../ServiceDiscovery+TypeErased.swift | 136 ++++---- .../ServiceDiscovery/ServiceDiscovery.swift | 53 ++-- .../AsyncAwaitTests.swift | 18 +- .../FilterInstanceServiceDiscoveryTests.swift | 257 ++++++++++----- Tests/ServiceDiscoveryTests/Helpers.swift | 35 +-- .../InMemoryServiceDiscoveryTests.swift | 226 +++++++++---- .../MapInstanceServiceDiscoveryTests.swift | 267 +++++++++++----- .../MapServiceServiceDiscoveryTests.swift | 297 ++++++++++++------ .../TypeErasedServiceDiscoveryTests.swift | 199 ++++++++---- docker/Dockerfile | 28 -- docker/docker-compose.2004.56.yaml | 18 -- docker/docker-compose.2004.57.yaml | 18 -- docker/docker-compose.2004.58.yaml | 18 -- docker/docker-compose.2204.510.yaml | 18 -- docker/docker-compose.2204.59.yaml | 19 -- docker/docker-compose.2204.main.yaml | 18 -- docker/docker-compose.yaml | 37 --- scripts/check_no_api_breakages.sh | 136 -------- scripts/generate_contributors_list.sh | 53 ---- scripts/generate_docs.sh | 126 -------- scripts/preview_docc.sh | 30 -- scripts/soundness.sh | 143 --------- 39 files changed, 1344 insertions(+), 1383 deletions(-) create mode 100644 .github/workflows/main.yml create mode 100644 .github/workflows/pull_request.yml create mode 100644 .github/workflows/pull_request_label.yml create mode 100644 .licenseignore create mode 100644 .swift-format delete mode 100644 .swiftformat delete mode 100644 CONTRIBUTORS.txt delete mode 100644 docker/Dockerfile delete mode 100644 docker/docker-compose.2004.56.yaml delete mode 100644 docker/docker-compose.2004.57.yaml delete mode 100644 docker/docker-compose.2004.58.yaml delete mode 100644 docker/docker-compose.2204.510.yaml delete mode 100644 docker/docker-compose.2204.59.yaml delete mode 100644 docker/docker-compose.2204.main.yaml delete mode 100644 docker/docker-compose.yaml delete mode 100755 scripts/check_no_api_breakages.sh delete mode 100755 scripts/generate_contributors_list.sh delete mode 100755 scripts/generate_docs.sh delete mode 100755 scripts/preview_docc.sh delete mode 100755 scripts/soundness.sh diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml new file mode 100644 index 0000000..23b336a --- /dev/null +++ b/.github/workflows/main.yml @@ -0,0 +1,18 @@ +name: Main + +on: + push: + branches: [main] + schedule: + - cron: "0 8,20 * * *" + +jobs: + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_9_arguments_override: "--explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "--explicit-target-dependency-import-check error" + linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable -Xswiftc -warnings-as-errors" + linux_nightly_6_0_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" diff --git a/.github/workflows/pull_request.yml b/.github/workflows/pull_request.yml new file mode 100644 index 0000000..e3899f6 --- /dev/null +++ b/.github/workflows/pull_request.yml @@ -0,0 +1,26 @@ +name: PR + +on: + pull_request: + types: [opened, reopened, synchronize] + +jobs: + soundness: + name: Soundness + uses: swiftlang/github-workflows/.github/workflows/soundness.yml@main + with: + license_header_check_project_name: "SwiftServiceDiscovery" + + unit-tests: + name: Unit tests + uses: apple/swift-nio/.github/workflows/unit_tests.yml@main + with: + linux_5_9_arguments_override: "--explicit-target-dependency-import-check error" + linux_5_10_arguments_override: "--explicit-target-dependency-import-check error" + linux_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable -Xswiftc -warnings-as-errors" + linux_nightly_6_0_arguments_override: "--explicit-target-dependency-import-check error -Xswiftc -require-explicit-sendable" + linux_nightly_main_enabled: false + + cxx-interop: + name: Cxx interop + uses: apple/swift-nio/.github/workflows/cxx_interop.yml@main diff --git a/.github/workflows/pull_request_label.yml b/.github/workflows/pull_request_label.yml new file mode 100644 index 0000000..86f199f --- /dev/null +++ b/.github/workflows/pull_request_label.yml @@ -0,0 +1,18 @@ +name: PR label + +on: + pull_request: + types: [labeled, unlabeled, opened, reopened, synchronize] + +jobs: + semver-label-check: + name: Semantic Version label check + runs-on: ubuntu-latest + timeout-minutes: 1 + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + persist-credentials: false + - name: Check for Semantic Version label + uses: apple/swift-nio/.github/actions/pull_request_semver_label_checker@main diff --git a/.licenseignore b/.licenseignore new file mode 100644 index 0000000..f284296 --- /dev/null +++ b/.licenseignore @@ -0,0 +1,9 @@ +.gitignore +.licenseignore +.swiftformatignore +.spi.yml +.swift-format +.github/ +**.md +**.txt +**Package.swift diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..3213ba6 --- /dev/null +++ b/.swift-format @@ -0,0 +1,61 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : true, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : true, + "lineLength" : 120, + "maximumBlankLines" : 1, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : false, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : true, + "AlwaysUseLowerCamelCase" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : false, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : false, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : true, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : false, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : true, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : false, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : false, + "UseSynthesizedInitializer" : true, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : true + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 8, + "version" : 1 +} diff --git a/.swiftformat b/.swiftformat deleted file mode 100644 index 68c5e7a..0000000 --- a/.swiftformat +++ /dev/null @@ -1,20 +0,0 @@ ---swiftversion 5.2 - -# file options - ---exclude .build - -# format options - ---ifdef no-indent ---indent 4 ---patternlet inline ---self insert ---stripunusedargs closure-only ---wraparguments before-first - -# rules - ---disable blankLinesAroundMark ---disable wrapMultilineStatementBraces - diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index be4ad74..8197e29 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -57,17 +57,45 @@ A good SwiftServiceDiscovery patch is: 3. Documented, adding API documentation as needed to cover new functions and properties. 4. Accompanied by a great commit message, using our commit message template. -### Commit Message Template +### Run CI checks locally -We require that your commit messages match our template. The easiest way to do that is to get git to help you by explicitly using the template. To do that, `cd` to the root of our repository and run: +You can run the Github Actions workflows locally using +[act](https://github.com/nektos/act). To run all the jobs that run on a pull +request, use the following command: - git config commit.template dev/git.commit.template +``` +% act pull_request +``` + +To run just a single job, use `workflow_call -j `, and specify the inputs +the job expects. For example, to run just shellcheck: + +``` +% act workflow_call -j soundness --input shell_check_enabled=true +``` + +To bind-mount the working directory to the container, rather than a copy, use +`--bind`. For example, to run just the formatting, and have the results +reflected in your working directory: + +``` +% act --bind workflow_call -j soundness --input format_check_enabled=true +``` + +If you'd like `act` to always run with certain flags, these can be be placed in +an `.actrc` file either in the current working directory or your home +directory, for example: + +``` +--container-architecture=linux/amd64 +--remote-name upstream +--action-offline-mode +``` ### Make sure Tests work on Linux SwiftServiceDiscovery uses XCTest to run tests on both macOS and Linux. - ## How to contribute your work Please open a pull request at https://github.com/apple/swift-service-discovery. Make sure the CI passes, and then wait for code review. diff --git a/CONTRIBUTORS.txt b/CONTRIBUTORS.txt deleted file mode 100644 index c20bffc..0000000 --- a/CONTRIBUTORS.txt +++ /dev/null @@ -1,19 +0,0 @@ -For the purpose of tracking copyright, this is the list of individuals and -organizations who have contributed source code to the Swift Service Discovery. - -For employees of an organization/company where the copyright of work done -by employees of that company is held by the company itself, only the company -needs to be listed here. - -## COPYRIGHT HOLDERS - -- Apple Inc. (all contributors with '@apple.com') - -### Contributors - -- Cory Benfield -- Yim Lee - -**Updating this list** - -Please do not edit this file manually. It is generated using `./scripts/generate_contributors_list.sh`. If a name is misspelled or appearing multiple times: add an entry in `./.mailmap` diff --git a/Package.swift b/Package.swift index b7dfae5..ec47710 100644 --- a/Package.swift +++ b/Package.swift @@ -1,4 +1,4 @@ -// swift-tools-version:5.6 +// swift-tools-version:5.9 //===----------------------------------------------------------------------===// // @@ -18,19 +18,18 @@ import PackageDescription let package = Package( name: "swift-service-discovery", - products: [ - .library(name: "ServiceDiscovery", targets: ["ServiceDiscovery"]), - ], + products: [.library(name: "ServiceDiscovery", targets: ["ServiceDiscovery"])], dependencies: [ .package(url: "https://github.com/apple/swift-atomics", from: "1.0.2"), .package(url: "https://github.com/apple/swift-log", from: "1.2.0"), - .package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"), ], targets: [ - .target(name: "ServiceDiscovery", dependencies: [ - .product(name: "Atomics", package: "swift-atomics"), - .product(name: "Logging", package: "swift-log"), - ]), + .target( + name: "ServiceDiscovery", + dependencies: [ + .product(name: "Atomics", package: "swift-atomics"), .product(name: "Logging", package: "swift-log"), + ] + ), .testTarget(name: "ServiceDiscoveryTests", dependencies: ["ServiceDiscovery"]), ] diff --git a/README.md b/README.md index 9e056fc..9008d42 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,7 @@ let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) To add a dependency on the API package, you need to declare it in your `Package.swift`: ```swift -.package(url: "https://github.com/apple/swift-service-discovery.git", from: "0.1.0"), +.package(url: "https://github.com/apple/swift-service-discovery.git", from: "1.3.0"), ``` and to your library target, add "ServiceDiscovery" to your dependencies: diff --git a/Sources/ServiceDiscovery/FilterInstanceServiceDiscovery.swift b/Sources/ServiceDiscovery/FilterInstanceServiceDiscovery.swift index ad459a1..9f6178f 100644 --- a/Sources/ServiceDiscovery/FilterInstanceServiceDiscovery.swift +++ b/Sources/ServiceDiscovery/FilterInstanceServiceDiscovery.swift @@ -14,6 +14,7 @@ import Dispatch +/// A service discovery implementation that filters instances using a predicate. public final class FilterInstanceServiceDiscovery { typealias Predicate = (BaseDiscovery.Instance) throws -> Bool @@ -28,15 +29,47 @@ public final class FilterInstanceServiceDiscovery) -> Void) { - self.originalSD.lookup(service, deadline: deadline) { result in callback(self.transform(result)) } - } + /// Performs a lookup for the given service's instances. The result will be sent to `callback`. + /// + /// ``defaultLookupTimeout`` will be used to compute `deadline` in case one is not specified. + /// + /// ### Threading + /// + /// `callback` may be invoked on arbitrary threads, as determined by implementation. + /// + /// - Parameters: + /// - service: The service to lookup + /// - deadline: Lookup is considered to have timed out if it does not complete by this time + /// - callback: The closure to receive lookup result + public func lookup( + _ service: BaseDiscovery.Service, + deadline: DispatchTime?, + callback: @escaping (Result<[BaseDiscovery.Instance], Error>) -> Void + ) { self.originalSD.lookup(service, deadline: deadline) { result in callback(self.transform(result)) } } - public func subscribe(to service: BaseDiscovery.Service, onNext nextResultHandler: @escaping (Result<[BaseDiscovery.Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken { + /// Subscribes to receive a service's instances whenever they change. + /// + /// The service's current list of instances will be sent to `nextResultHandler` when this method is first called. Subsequently, + /// `nextResultHandler` will only be invoked when the `service`'s instances change. + /// + /// ### Threading + /// + /// `nextResultHandler` and `completionHandler` may be invoked on arbitrary threads, as determined by implementation. + /// + /// - Parameters: + /// - service: The service to subscribe to + /// - nextResultHandler: The closure to receive update result + /// - completionHandler: The closure to invoke when the subscription completes (e.g., when the `ServiceDiscovery` instance exits, etc.), + /// including cancellation requested through `CancellationToken`. + /// + /// - Returns: A ``CancellationToken`` instance that can be used to cancel the subscription in the future. + public func subscribe( + to service: BaseDiscovery.Service, + onNext nextResultHandler: @escaping (Result<[BaseDiscovery.Instance], Error>) -> Void, + onComplete completionHandler: @escaping (CompletionReason) -> Void + ) -> CancellationToken { self.originalSD.subscribe( to: service, onNext: { result in nextResultHandler(self.transform(result)) }, @@ -44,16 +77,12 @@ extension FilterInstanceServiceDiscovery: ServiceDiscovery { ) } - private func transform(_ result: Result<[BaseDiscovery.Instance], Error>) -> Result<[BaseDiscovery.Instance], Error> { + private func transform(_ result: Result<[BaseDiscovery.Instance], Error>) -> Result<[BaseDiscovery.Instance], Error> + { switch result { case .success(let instances): - do { - return try .success(instances.filter(self.predicate)) - } catch { - return .failure(error) - } - case .failure(let error): - return .failure(error) + do { return try .success(instances.filter(self.predicate)) } catch { return .failure(error) } + case .failure(let error): return .failure(error) } } } diff --git a/Sources/ServiceDiscovery/HostPort.swift b/Sources/ServiceDiscovery/HostPort.swift index fcc12bd..ae75a40 100644 --- a/Sources/ServiceDiscovery/HostPort.swift +++ b/Sources/ServiceDiscovery/HostPort.swift @@ -22,7 +22,5 @@ public struct HostPort: Hashable, CustomStringConvertible { self.port = port } - public var description: String { - "\(self.host):\(self.port)" - } + public var description: String { "\(self.host):\(self.port)" } } diff --git a/Sources/ServiceDiscovery/InMemoryServiceDiscovery.swift b/Sources/ServiceDiscovery/InMemoryServiceDiscovery.swift index 9e606eb..5284fd9 100644 --- a/Sources/ServiceDiscovery/InMemoryServiceDiscovery.swift +++ b/Sources/ServiceDiscovery/InMemoryServiceDiscovery.swift @@ -14,7 +14,7 @@ import Atomics import Dispatch -import Foundation // for NSLock +import Foundation // for NSLock /// Provides lookup for service instances that are stored in-memory. public class InMemoryServiceDiscovery: ServiceDiscovery { @@ -27,30 +27,29 @@ public class InMemoryServiceDiscovery: Se private let queue: DispatchQueue - public var defaultLookupTimeout: DispatchTimeInterval { - self.configuration.defaultLookupTimeout - } + public var defaultLookupTimeout: DispatchTimeInterval { self.configuration.defaultLookupTimeout } - public var isShutdown: Bool { - self.lock.withLock { - self._isShutdown - } - } + public var isShutdown: Bool { self.lock.withLock { self._isShutdown } } - public init(configuration: Configuration, queue: DispatchQueue = .init(label: "InMemoryServiceDiscovery", attributes: .concurrent)) { + public init( + configuration: Configuration, + queue: DispatchQueue = .init(label: "InMemoryServiceDiscovery", attributes: .concurrent) + ) { self.configuration = configuration self._serviceInstances = configuration.serviceInstances self.queue = queue } - public func lookup(_ service: Service, deadline: DispatchTime? = nil, callback: @escaping (Result<[Instance], Error>) -> Void) { + public func lookup( + _ service: Service, + deadline: DispatchTime? = nil, + callback: @escaping (Result<[Instance], Error>) -> Void + ) { let isDone = ManagedAtomic(false) let lookupWorkItem = DispatchWorkItem { let result = self.lock.withLock { () -> Result<[Instance], Error> in - if self._isShutdown { - return .failure(ServiceDiscoveryError.unavailable) - } + if self._isShutdown { return .failure(ServiceDiscoveryError.unavailable) } if let instances = self._lookupNow(service) { return .success(instances) @@ -81,8 +80,7 @@ public class InMemoryServiceDiscovery: Se case yieldFirstElement([Instance]?) } - @discardableResult - public func subscribe( + @discardableResult public func subscribe( to service: Service, onNext nextResultHandler: @escaping (Result<[Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void = { _ in } @@ -95,9 +93,7 @@ public class InMemoryServiceDiscovery: Se ) let action = self.lock.withLock { () -> SubscribeAction in - guard !self._isShutdown else { - return .cancelSinceShutdown - } + guard !self._isShutdown else { return .cancelSinceShutdown } // first add the subscription var subscriptions = self._serviceSubscriptions.removeValue(forKey: service) ?? [Subscription]() @@ -142,9 +138,7 @@ public class InMemoryServiceDiscovery: Se public func shutdown() { let maybeServiceSubscriptions = self.lock.withLock { () -> Dictionary.Values? in - if self._isShutdown { - return nil - } + if self._isShutdown { return nil } self._isShutdown = true let subscriptions = self._serviceSubscriptions @@ -153,9 +147,7 @@ public class InMemoryServiceDiscovery: Se return subscriptions.values } - guard let serviceSubscriptions = maybeServiceSubscriptions else { - return - } + guard let serviceSubscriptions = maybeServiceSubscriptions else { return } for subscriptions in serviceSubscriptions { for sub in subscriptions.lazy.filter({ !$0.cancellationToken.isCancelled }) { @@ -165,11 +157,7 @@ public class InMemoryServiceDiscovery: Se } private func _lookupNow(_ service: Service) -> [Instance]? { - if let instances = self._serviceInstances[service] { - return instances - } else { - return nil - } + if let instances = self._serviceInstances[service] { return instances } else { return nil } } private struct Subscription { @@ -182,23 +170,17 @@ public class InMemoryServiceDiscovery: Se public extension InMemoryServiceDiscovery { struct Configuration { /// Default configuration - public static var `default`: Configuration { - .init() - } + public static var `default`: Configuration { .init() } /// Lookup timeout in case `deadline` is not specified public var defaultLookupTimeout: DispatchTimeInterval = .milliseconds(100) internal var serviceInstances: [Service: [Instance]] - public init() { - self.init(serviceInstances: [:]) - } + public init() { self.init(serviceInstances: [:]) } /// Initializes `InMemoryServiceDiscovery` with the given service to instances mappings. - public init(serviceInstances: [Service: [Instance]]) { - self.serviceInstances = serviceInstances - } + public init(serviceInstances: [Service: [Instance]]) { self.serviceInstances = serviceInstances } /// Registers `service` and its `instances`. public mutating func register(service: Service, instances: [Instance]) { @@ -212,9 +194,7 @@ public extension InMemoryServiceDiscovery { private extension NSLock { func withLock(_ body: () throws -> Result) rethrows -> Result { self.lock() - defer { - self.unlock() - } + defer { self.unlock() } return try body() } } diff --git a/Sources/ServiceDiscovery/MapInstanceServiceDiscovery.swift b/Sources/ServiceDiscovery/MapInstanceServiceDiscovery.swift index 280f476..6829c14 100644 --- a/Sources/ServiceDiscovery/MapInstanceServiceDiscovery.swift +++ b/Sources/ServiceDiscovery/MapInstanceServiceDiscovery.swift @@ -14,6 +14,7 @@ import Dispatch +/// A service discovery implementation that maps instances to another type using a closure. public final class MapInstanceServiceDiscovery { typealias Transformer = (BaseDiscovery.Instance) throws -> DerivedInstance @@ -28,15 +29,47 @@ public final class MapInstanceServiceDiscovery) -> Void) { - self.originalSD.lookup(service, deadline: deadline) { result in callback(self.transform(result)) } - } + /// Performs a lookup for the given service's instances. The result will be sent to `callback`. + /// + /// ``defaultLookupTimeout`` will be used to compute `deadline` in case one is not specified. + /// + /// ### Threading + /// + /// `callback` may be invoked on arbitrary threads, as determined by implementation. + /// + /// - Parameters: + /// - service: The service to lookup + /// - deadline: Lookup is considered to have timed out if it does not complete by this time + /// - callback: The closure to receive lookup result + public func lookup( + _ service: BaseDiscovery.Service, + deadline: DispatchTime?, + callback: @escaping (Result<[DerivedInstance], Error>) -> Void + ) { self.originalSD.lookup(service, deadline: deadline) { result in callback(self.transform(result)) } } - public func subscribe(to service: BaseDiscovery.Service, onNext nextResultHandler: @escaping (Result<[DerivedInstance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken { + /// Subscribes to receive a service's instances whenever they change. + /// + /// The service's current list of instances will be sent to `nextResultHandler` when this method is first called. Subsequently, + /// `nextResultHandler` will only be invoked when the `service`'s instances change. + /// + /// ### Threading + /// + /// `nextResultHandler` and `completionHandler` may be invoked on arbitrary threads, as determined by implementation. + /// + /// - Parameters: + /// - service: The service to subscribe to + /// - nextResultHandler: The closure to receive update result + /// - completionHandler: The closure to invoke when the subscription completes (e.g., when the `ServiceDiscovery` instance exits, etc.), + /// including cancellation requested through `CancellationToken`. + /// + /// - Returns: A ``CancellationToken`` instance that can be used to cancel the subscription in the future. + public func subscribe( + to service: BaseDiscovery.Service, + onNext nextResultHandler: @escaping (Result<[DerivedInstance], Error>) -> Void, + onComplete completionHandler: @escaping (CompletionReason) -> Void + ) -> CancellationToken { self.originalSD.subscribe( to: service, onNext: { result in nextResultHandler(self.transform(result)) }, @@ -47,13 +80,8 @@ extension MapInstanceServiceDiscovery: ServiceDiscovery { private func transform(_ result: Result<[BaseDiscovery.Instance], Error>) -> Result<[DerivedInstance], Error> { switch result { case .success(let instances): - do { - return try .success(instances.map(self.transformer)) - } catch { - return .failure(error) - } - case .failure(let error): - return .failure(error) + do { return try .success(instances.map(self.transformer)) } catch { return .failure(error) } + case .failure(let error): return .failure(error) } } } diff --git a/Sources/ServiceDiscovery/MapServiceServiceDiscovery.swift b/Sources/ServiceDiscovery/MapServiceServiceDiscovery.swift index dd049a7..e7e2c6c 100644 --- a/Sources/ServiceDiscovery/MapServiceServiceDiscovery.swift +++ b/Sources/ServiceDiscovery/MapServiceServiceDiscovery.swift @@ -14,6 +14,7 @@ import Dispatch +/// A service discovery implementation that maps services using a closure. public final class MapServiceServiceDiscovery { typealias Transformer = (ComputedService) throws -> BaseDiscovery.Service @@ -28,16 +29,28 @@ public final class MapServiceServiceDiscovery) -> Void) { + /// Performs a lookup for the given service's instances. The result will be sent to `callback`. + /// + /// ``defaultLookupTimeout`` will be used to compute `deadline` in case one is not specified. + /// + /// ### Threading + /// + /// `callback` may be invoked on arbitrary threads, as determined by implementation. + /// + /// - Parameters: + /// - service: The service to lookup + /// - deadline: Lookup is considered to have timed out if it does not complete by this time + /// - callback: The closure to receive lookup result + public func lookup( + _ service: ComputedService, + deadline: DispatchTime?, + callback: @escaping (Result<[BaseDiscovery.Instance], Error>) -> Void + ) { let derivedService: BaseDiscovery.Service - do { - derivedService = try self.transformer(service) - } catch { + do { derivedService = try self.transformer(service) } catch { callback(.failure(error)) return } @@ -45,12 +58,30 @@ extension MapServiceServiceDiscovery: ServiceDiscovery { self.originalSD.lookup(derivedService, deadline: deadline, callback: callback) } - public func subscribe(to service: ComputedService, onNext nextResultHandler: @escaping (Result<[BaseDiscovery.Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken { + /// Subscribes to receive a service's instances whenever they change. + /// + /// The service's current list of instances will be sent to `nextResultHandler` when this method is first called. Subsequently, + /// `nextResultHandler` will only be invoked when the `service`'s instances change. + /// + /// ### Threading + /// + /// `nextResultHandler` and `completionHandler` may be invoked on arbitrary threads, as determined by implementation. + /// + /// - Parameters: + /// - service: The service to subscribe to + /// - nextResultHandler: The closure to receive update result + /// - completionHandler: The closure to invoke when the subscription completes (e.g., when the `ServiceDiscovery` instance exits, etc.), + /// including cancellation requested through `CancellationToken`. + /// + /// - Returns: A ``CancellationToken`` instance that can be used to cancel the subscription in the future. + public func subscribe( + to service: ComputedService, + onNext nextResultHandler: @escaping (Result<[BaseDiscovery.Instance], Error>) -> Void, + onComplete completionHandler: @escaping (CompletionReason) -> Void + ) -> CancellationToken { let derivedService: BaseDiscovery.Service - do { - derivedService = try self.transformer(service) - } catch { + do { derivedService = try self.transformer(service) } catch { // Ok, we couldn't transform the service. We want to throw an error into `nextResultHandler` and then immediately cancel. let cancellationToken = CancellationToken(isCancelled: true, completionHandler: completionHandler) nextResultHandler(.failure(error)) @@ -58,10 +89,6 @@ extension MapServiceServiceDiscovery: ServiceDiscovery { return cancellationToken } - return self.originalSD.subscribe( - to: derivedService, - onNext: nextResultHandler, - onComplete: completionHandler - ) + return self.originalSD.subscribe(to: derivedService, onNext: nextResultHandler, onComplete: completionHandler) } } diff --git a/Sources/ServiceDiscovery/ServiceDiscovery+AsyncAwait.swift b/Sources/ServiceDiscovery/ServiceDiscovery+AsyncAwait.swift index 3600558..1d008c3 100644 --- a/Sources/ServiceDiscovery/ServiceDiscovery+AsyncAwait.swift +++ b/Sources/ServiceDiscovery/ServiceDiscovery+AsyncAwait.swift @@ -14,8 +14,6 @@ import Dispatch -#if compiler(>=5.5) && canImport(_Concurrency) - public extension ServiceDiscovery { /// Performs async lookup for the given service's instances. /// @@ -23,17 +21,17 @@ public extension ServiceDiscovery { /// /// - Parameters: /// - service: The service to lookup - /// + /// - deadline: Lookup is considered to have timed out if it does not complete by this time /// - Returns: A listing of service instances. - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func lookup(_ service: Service, deadline: DispatchTime? = nil) async throws -> [Instance] { + /// - Throws: An error if the lookup fails. + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func lookup(_ service: Service, deadline: DispatchTime? = nil) + async throws -> [Instance] + { try await withCheckedThrowingContinuation { (continuation: CheckedContinuation<[Instance], Error>) in self.lookup(service, deadline: deadline) { result in switch result { - case .success(let instances): - continuation.resume(returning: instances) - case .failure(let error): - continuation.resume(throwing: error) + case .success(let instances): continuation.resume(returning: instances) + case .failure(let error): continuation.resume(throwing: error) } } } @@ -41,87 +39,68 @@ public extension ServiceDiscovery { /// Returns a ``ServiceSnapshots``, which is an `AsyncSequence` and each of its items is a snapshot listing of service instances. /// - /// - Parameters: - /// - service: The service to subscribe to + /// - Parameter service: The service to subscribe to /// /// - Returns: A ``ServiceSnapshots`` async sequence. - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func subscribe(to service: Service) -> ServiceSnapshots { - ServiceSnapshots(AsyncThrowingStream<[Instance], Error> { continuation in - let cancellationToken = self.subscribe( - to: service, - onNext: { result in - switch result { - case .success(let instances): - continuation.yield(instances) - case .failure(let error): - // LookupError is recoverable (e.g., service is added *after* subscription begins), so don't give up yet - guard error is LookupError else { - return continuation.finish(throwing: error) + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func subscribe(to service: Service) -> ServiceSnapshots< + Instance + > { + ServiceSnapshots( + AsyncThrowingStream<[Instance], Error> { continuation in + let cancellationToken = self.subscribe( + to: service, + onNext: { result in + switch result { + case .success(let instances): continuation.yield(instances) + case .failure(let error): + // LookupError is recoverable (e.g., service is added *after* subscription begins), so don't give up yet + guard error is LookupError else { return continuation.finish(throwing: error) } + } + }, + onComplete: { reason in + switch reason { + case .cancellationRequested: continuation.finish() + case .serviceDiscoveryUnavailable: + continuation.finish(throwing: ServiceDiscoveryError.unavailable) + default: continuation.finish(throwing: ServiceDiscoveryError.other(reason.description)) } } - }, - onComplete: { reason in - switch reason { - case .cancellationRequested: - continuation.finish() - case .serviceDiscoveryUnavailable: - continuation.finish(throwing: ServiceDiscoveryError.unavailable) - default: - continuation.finish(throwing: ServiceDiscoveryError.other(reason.description)) - } - } - ) + ) - continuation.onTermination = { @Sendable (_) in - cancellationToken.cancel() + continuation.onTermination = { @Sendable (_) in cancellationToken.cancel() } } - }) + ) } } /// An async sequence of snapshot listings of service instances. -@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) -public struct ServiceSnapshots: AsyncSequence { +@available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) public struct ServiceSnapshots: AsyncSequence { public typealias Element = [Instance] typealias AsyncSnapshotsStream = AsyncThrowingStream private let stream: AsyncSnapshotsStream - public init(_ snapshots: SnapshotSequence) where SnapshotSequence.Element == Element { + public init(_ snapshots: SnapshotSequence) + where SnapshotSequence.Element == Element { self.stream = AsyncThrowingStream { continuation in let task = Task { do { - for try await snapshot in snapshots { - continuation.yield(snapshot) - } + for try await snapshot in snapshots { continuation.yield(snapshot) } continuation.finish() - } catch { - continuation.finish(throwing: error) - } + } catch { continuation.finish(throwing: error) } } - continuation.onTermination = { _ in - task.cancel() - } + continuation.onTermination = { _ in task.cancel() } } } - public func makeAsyncIterator() -> AsyncIterator { - AsyncIterator(self.stream.makeAsyncIterator()) - } + public func makeAsyncIterator() -> AsyncIterator { AsyncIterator(self.stream.makeAsyncIterator()) } public struct AsyncIterator: AsyncIteratorProtocol { private var underlying: AsyncSnapshotsStream.Iterator - init(_ iterator: AsyncSnapshotsStream.Iterator) { - self.underlying = iterator - } + init(_ iterator: AsyncSnapshotsStream.Iterator) { self.underlying = iterator } - public mutating func next() async throws -> [Instance]? { - try await self.underlying.next() - } + public mutating func next() async throws -> [Instance]? { try await self.underlying.next() } } } - -#endif diff --git a/Sources/ServiceDiscovery/ServiceDiscovery+Combinators.swift b/Sources/ServiceDiscovery/ServiceDiscovery+Combinators.swift index 6e33212..6f706ba 100644 --- a/Sources/ServiceDiscovery/ServiceDiscovery+Combinators.swift +++ b/Sources/ServiceDiscovery/ServiceDiscovery+Combinators.swift @@ -19,9 +19,9 @@ public extension ServiceDiscovery { /// the derived function. /// /// It is not necessarily safe to block in this closure. This closure should not block for safety. - func mapInstance(_ transformer: @escaping (Instance) throws -> DerivedInstance) -> MapInstanceServiceDiscovery { - MapInstanceServiceDiscovery(originalSD: self, transformer: transformer) - } + func mapInstance(_ transformer: @escaping (Instance) throws -> DerivedInstance) + -> MapInstanceServiceDiscovery + { MapInstanceServiceDiscovery(originalSD: self, transformer: transformer) } /// Creates a new ``ServiceDiscovery/ServiceDiscovery`` implementation based on this one, transforming the services according to /// the derived function. diff --git a/Sources/ServiceDiscovery/ServiceDiscovery+TypeErased.swift b/Sources/ServiceDiscovery/ServiceDiscovery+TypeErased.swift index 14afe5c..263f269 100644 --- a/Sources/ServiceDiscovery/ServiceDiscovery+TypeErased.swift +++ b/Sources/ServiceDiscovery/ServiceDiscovery+TypeErased.swift @@ -24,14 +24,14 @@ public class ServiceDiscoveryBox: Service private let _lookup: (Service, DispatchTime?, @escaping (Result<[Instance], Error>) -> Void) -> Void - private let _subscribe: (Service, @escaping (Result<[Instance], Error>) -> Void, @escaping (CompletionReason) -> Void) -> CancellationToken + private let _subscribe: + (Service, @escaping (Result<[Instance], Error>) -> Void, @escaping (CompletionReason) -> Void) -> + CancellationToken - public var defaultLookupTimeout: DispatchTimeInterval { - self._defaultLookupTimeout() - } + public var defaultLookupTimeout: DispatchTimeInterval { self._defaultLookupTimeout() } public init(_ serviceDiscovery: ServiceDiscoveryImpl) - where ServiceDiscoveryImpl.Service == Service, ServiceDiscoveryImpl.Instance == Instance { + where ServiceDiscoveryImpl.Service == Service, ServiceDiscoveryImpl.Instance == Instance { self._underlying = serviceDiscovery self._defaultLookupTimeout = { serviceDiscovery.defaultLookupTimeout } @@ -43,27 +43,29 @@ public class ServiceDiscoveryBox: Service } } - public func lookup(_ service: Service, deadline: DispatchTime? = nil, callback: @escaping (Result<[Instance], Error>) -> Void) { - self._lookup(service, deadline, callback) - } + public func lookup( + _ service: Service, + deadline: DispatchTime? = nil, + callback: @escaping (Result<[Instance], Error>) -> Void + ) { self._lookup(service, deadline, callback) } - @discardableResult - public func subscribe( + @discardableResult public func subscribe( to service: Service, onNext nextResultHandler: @escaping (Result<[Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void = { _ in } - ) -> CancellationToken { - self._subscribe(service, nextResultHandler, completionHandler) - } + ) -> CancellationToken { self._subscribe(service, nextResultHandler, completionHandler) } /// Unwraps the underlying ``ServiceDiscovery/ServiceDiscovery`` instance as `ServiceDiscoveryImpl` type. /// /// - Throws: `TypeErasedServiceDiscoveryError.typeMismatch` when the underlying /// `ServiceDiscovery` instance is not of type `ServiceDiscoveryImpl`. - @discardableResult - public func unwrapAs(_ serviceDiscoveryType: ServiceDiscoveryImpl.Type) throws -> ServiceDiscoveryImpl { + @discardableResult public func unwrapAs( + _ serviceDiscoveryType: ServiceDiscoveryImpl.Type + ) throws -> ServiceDiscoveryImpl { guard let unwrapped = self._underlying as? ServiceDiscoveryImpl else { - throw TypeErasedServiceDiscoveryError.typeMismatch(description: "Cannot unwrap [\(type(of: self._underlying)))] as [\(ServiceDiscoveryImpl.self)]") + throw TypeErasedServiceDiscoveryError.typeMismatch( + description: "Cannot unwrap [\(type(of: self._underlying)))] as [\(ServiceDiscoveryImpl.self)]" + ) } return unwrapped } @@ -79,11 +81,11 @@ public class AnyServiceDiscovery: ServiceDiscovery { private let _lookup: (AnyHashable, DispatchTime?, @escaping (Result<[AnyHashable], Error>) -> Void) -> Void - private let _subscribe: (AnyHashable, @escaping (Result<[AnyHashable], Error>) -> Void, @escaping (CompletionReason) -> Void) -> CancellationToken + private let _subscribe: + (AnyHashable, @escaping (Result<[AnyHashable], Error>) -> Void, @escaping (CompletionReason) -> Void) -> + CancellationToken - public var defaultLookupTimeout: DispatchTimeInterval { - self._defaultLookupTimeout() - } + public var defaultLookupTimeout: DispatchTimeInterval { self._defaultLookupTimeout() } public init(_ serviceDiscovery: ServiceDiscoveryImpl) { self._underlying = serviceDiscovery @@ -91,7 +93,9 @@ public class AnyServiceDiscovery: ServiceDiscovery { self._lookup = { anyService, deadline, callback in guard let service = anyService.base as? ServiceDiscoveryImpl.Service else { - preconditionFailure("Expected service type to be \(ServiceDiscoveryImpl.Service.self), got \(type(of: anyService.base))") + preconditionFailure( + "Expected service type to be \(ServiceDiscoveryImpl.Service.self), got \(type(of: anyService.base))" + ) } serviceDiscovery.lookup(service, deadline: deadline) { result in callback(result.map { $0.map(AnyHashable.init) }) @@ -99,7 +103,9 @@ public class AnyServiceDiscovery: ServiceDiscovery { } self._subscribe = { anyService, nextResultHandler, completionHandler in guard let service = anyService.base as? ServiceDiscoveryImpl.Service else { - preconditionFailure("Expected service type to be \(ServiceDiscoveryImpl.Service.self), got \(type(of: anyService.base))") + preconditionFailure( + "Expected service type to be \(ServiceDiscoveryImpl.Service.self), got \(type(of: anyService.base))" + ) } return serviceDiscovery.subscribe( to: service, @@ -112,9 +118,11 @@ public class AnyServiceDiscovery: ServiceDiscovery { /// See ``ServiceDiscovery/lookup(_:deadline:callback:)``. /// /// - Warning: If `service` type does not match the underlying `ServiceDiscovery`'s, it would result in a failure. - public func lookup(_ service: AnyHashable, deadline: DispatchTime? = nil, callback: @escaping (Result<[AnyHashable], Error>) -> Void) { - self._lookup(service, deadline, callback) - } + public func lookup( + _ service: AnyHashable, + deadline: DispatchTime? = nil, + callback: @escaping (Result<[AnyHashable], Error>) -> Void + ) { self._lookup(service, deadline, callback) } /// See ``ServiceDiscovery/lookup(_:deadline:callback:)``. /// @@ -124,45 +132,42 @@ public class AnyServiceDiscovery: ServiceDiscovery { deadline: DispatchTime? = nil, callback: @escaping (Result<[Instance], Error>) -> Void ) where Service: Hashable, Instance: Hashable { - self._lookup(AnyHashable(service), deadline) { result in - callback(self.transform(result)) - } + self._lookup(AnyHashable(service), deadline) { result in callback(self.transform(result)) } } /// See ``ServiceDiscovery/subscribe(to:onNext:onComplete:)``. /// /// - Warning: If `service` type does not match the underlying ``ServiceDiscovery/ServiceDiscovery``'s, it would result in a failure. - @discardableResult - public func subscribe( + @discardableResult public func subscribe( to service: AnyHashable, onNext nextResultHandler: @escaping (Result<[AnyHashable], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void = { _ in } - ) -> CancellationToken { - self._subscribe(service, nextResultHandler, completionHandler) - } + ) -> CancellationToken { self._subscribe(service, nextResultHandler, completionHandler) } /// See ``ServiceDiscovery/subscribe(to:onNext:onComplete:)``. /// /// - Warning: If `Service` or `Instance` type does not match the underlying ``ServiceDiscovery/ServiceDiscovery``'s associated types, it would result in a failure. - @discardableResult - public func subscribeAndUnwrap( + @discardableResult public func subscribeAndUnwrap( to service: Service, onNext nextResultHandler: @escaping (Result<[Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void = { _ in } ) -> CancellationToken where Service: Hashable, Instance: Hashable { - self._subscribe(AnyHashable(service), { result in nextResultHandler(self.transform(result)) }, completionHandler) + self._subscribe( + AnyHashable(service), + { result in nextResultHandler(self.transform(result)) }, + completionHandler + ) } - private func transform(_ result: Result<[AnyHashable], Error>) -> Result<[Instance], Error> where Instance: Hashable { + private func transform(_ result: Result<[AnyHashable], Error>) -> Result<[Instance], Error> + where Instance: Hashable { result.flatMap { anyInstances in - var instances = [Instance]() + var instances: [Instance] = [] for anyInstance in anyInstances { do { let instance: Instance = try self.transform(anyInstance) instances.append(instance) - } catch { - return .failure(error) - } + } catch { return .failure(error) } } return .success(instances) } @@ -170,7 +175,9 @@ public class AnyServiceDiscovery: ServiceDiscovery { private func transform(_ anyInstance: AnyHashable) throws -> Instance where Instance: Hashable { guard let instance = anyInstance.base as? Instance else { - throw TypeErasedServiceDiscoveryError.typeMismatch(description: "Expected instance type to be \(Instance.self), got \(type(of: anyInstance.base))") + throw TypeErasedServiceDiscoveryError.typeMismatch( + description: "Expected instance type to be \(Instance.self), got \(type(of: anyInstance.base))" + ) } return instance } @@ -179,45 +186,48 @@ public class AnyServiceDiscovery: ServiceDiscovery { /// /// - Throws: `TypeErasedServiceDiscoveryError.typeMismatch` when the underlying /// `ServiceDiscovery` instance is not of type `ServiceDiscoveryImpl`. - public func unwrapAs(_ serviceDiscoveryType: ServiceDiscoveryImpl.Type) throws -> ServiceDiscoveryImpl { + public func unwrapAs(_ serviceDiscoveryType: ServiceDiscoveryImpl.Type) + throws -> ServiceDiscoveryImpl + { guard let unwrapped = self._underlying as? ServiceDiscoveryImpl else { - throw TypeErasedServiceDiscoveryError.typeMismatch(description: "Cannot unwrap [\(type(of: self._underlying))] as [\(ServiceDiscoveryImpl.self)]") + throw TypeErasedServiceDiscoveryError.typeMismatch( + description: "Cannot unwrap [\(type(of: self._underlying))] as [\(ServiceDiscoveryImpl.self)]" + ) } return unwrapped } } -#if compiler(>=5.5) && canImport(_Concurrency) public extension AnyServiceDiscovery { /// See ``ServiceDiscovery/lookup(_:deadline:)``. /// /// - Warning: If `Service` or `Instance` type does not match the underlying ``ServiceDiscovery/ServiceDiscovery``'s associated types, it would result in a failure. - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func lookupAndUnwrap(_ service: Service, deadline: DispatchTime? = nil) async throws -> [Instance] where Service: Hashable, Instance: Hashable { + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func lookupAndUnwrap( + _ service: Service, + deadline: DispatchTime? = nil + ) async throws -> [Instance] where Service: Hashable, Instance: Hashable { try await self.lookup(service, deadline: deadline).map(self.transform) } /// See ``ServiceDiscovery/subscribe(to:)``. /// /// - Warning: If `Service` or `Instance` type does not match the underlying ``ServiceDiscovery/ServiceDiscovery``'s associated types, it would result in a failure. - @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) - func subscribeAndUnwrap(to service: Service) -> ServiceSnapshots where Service: Hashable, Instance: Hashable { - ServiceSnapshots(AsyncThrowingStream { continuation in - Task { - do { - for try await snapshot in self.subscribe(to: service) { - continuation.yield(try snapshot.map(self.transform)) - } - continuation.finish() - } catch { - continuation.finish(throwing: error) + @available(macOS 12, iOS 15, tvOS 15, watchOS 8, *) func subscribeAndUnwrap(to service: Service) + -> ServiceSnapshots where Service: Hashable, Instance: Hashable + { + ServiceSnapshots( + AsyncThrowingStream { continuation in + Task { + do { + for try await snapshot in self.subscribe(to: service) { + continuation.yield(try snapshot.map(self.transform)) + } + continuation.finish() + } catch { continuation.finish(throwing: error) } } } - }) + ) } } -#endif -public enum TypeErasedServiceDiscoveryError: Error { - case typeMismatch(description: String) -} +public enum TypeErasedServiceDiscoveryError: Error { case typeMismatch(description: String) } diff --git a/Sources/ServiceDiscovery/ServiceDiscovery.swift b/Sources/ServiceDiscovery/ServiceDiscovery.swift index 01280c4..b818464 100644 --- a/Sources/ServiceDiscovery/ServiceDiscovery.swift +++ b/Sources/ServiceDiscovery/ServiceDiscovery.swift @@ -61,7 +61,11 @@ public protocol ServiceDiscovery: AnyObject { /// including cancellation requested through `CancellationToken`. /// /// - Returns: A ``CancellationToken`` instance that can be used to cancel the subscription in the future. - func subscribe(to service: Service, onNext nextResultHandler: @escaping (Result<[Instance], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void) -> CancellationToken + func subscribe( + to service: Service, + onNext nextResultHandler: @escaping (Result<[Instance], Error>) -> Void, + onComplete completionHandler: @escaping (CompletionReason) -> Void + ) -> CancellationToken } // MARK: - Subscription @@ -72,9 +76,7 @@ public class CancellationToken { private let _completionHandler: (CompletionReason) -> Void /// Returns `true` if the subscription has been cancelled. - public var isCancelled: Bool { - self._isCancelled.load(ordering: .acquiring) - } + public var isCancelled: Bool { self._isCancelled.load(ordering: .acquiring) } /// Creates a new token. public init(isCancelled: Bool = false, completionHandler: @escaping (CompletionReason) -> Void = { _ in }) { @@ -84,14 +86,14 @@ public class CancellationToken { /// Cancels the subscription. public func cancel() { - guard self._isCancelled.compareExchange(expected: false, desired: true, ordering: .acquiring).exchanged else { return } + guard self._isCancelled.compareExchange(expected: false, desired: true, ordering: .acquiring).exchanged else { + return + } self._completionHandler(.cancellationRequested) } } -#if compiler(>=5.5) && canImport(_Concurrency) extension CancellationToken: @unchecked Sendable {} -#endif /// Reason that leads to service discovery subscription completion. public struct CompletionReason: Equatable, CustomStringConvertible { @@ -102,21 +104,16 @@ public struct CompletionReason: Equatable, CustomStringConvertible { var description: String { switch self { - case .cancellationRequested: - return "cancellationRequested" - case .serviceDiscoveryUnavailable: - return "serviceDiscoveryUnavailable" - case .failedToMapService: - return "failedToMapService" + case .cancellationRequested: return "cancellationRequested" + case .serviceDiscoveryUnavailable: return "serviceDiscoveryUnavailable" + case .failedToMapService: return "failedToMapService" } } } internal let type: ReasonType - public var description: String { - "CompletionReason.\(String(describing: self.type))" - } + public var description: String { "CompletionReason.\(String(describing: self.type))" } /// Cancellation requested through `CancellationToken`. public static let cancellationRequested = CompletionReason(type: .cancellationRequested) @@ -138,19 +135,15 @@ public struct LookupError: Error, Equatable, CustomStringConvertible { var description: String { switch self { - case .unknownService: - return "unknownService" - case .timedOut: - return "timedOut" + case .unknownService: return "unknownService" + case .timedOut: return "timedOut" } } } internal let type: ErrorType - public var description: String { - "LookupError.\(String(describing: self.type))" - } + public var description: String { "LookupError.\(String(describing: self.type))" } /// Lookup cannot be completed because the service is unknown. public static let unknownService = LookupError(type: .unknownService) @@ -167,24 +160,18 @@ public struct ServiceDiscoveryError: Error, Equatable, CustomStringConvertible { var description: String { switch self { - case .unavailable: - return "unavailable" - case .other(let detail): - return "other: \(detail)" + case .unavailable: return "unavailable" + case .other(let detail): return "other: \(detail)" } } } internal let type: ErrorType - public var description: String { - "ServiceDiscoveryError.\(String(describing: self.type))" - } + public var description: String { "ServiceDiscoveryError.\(String(describing: self.type))" } /// `ServiceDiscovery` instance is unavailable. public static let unavailable = ServiceDiscoveryError(type: .unavailable) - public static func other(_ detail: String) -> ServiceDiscoveryError { - ServiceDiscoveryError(type: .other(detail)) - } + public static func other(_ detail: String) -> ServiceDiscoveryError { ServiceDiscoveryError(type: .other(detail)) } } diff --git a/Tests/ServiceDiscoveryTests/AsyncAwaitTests.swift b/Tests/ServiceDiscoveryTests/AsyncAwaitTests.swift index 626b9e2..64e6fdc 100644 --- a/Tests/ServiceDiscoveryTests/AsyncAwaitTests.swift +++ b/Tests/ServiceDiscoveryTests/AsyncAwaitTests.swift @@ -12,7 +12,6 @@ // //===----------------------------------------------------------------------===// -#if compiler(>=5.5) && canImport(_Concurrency) import Atomics import Dispatch import ServiceDiscovery @@ -32,11 +31,7 @@ final class MockServiceDiscovery: ServiceDiscovery { to service: String, onNext nextResultHandler: @escaping (Result<[String], Error>) -> Void, onComplete completionHandler: @escaping (CompletionReason) -> Void - ) -> CancellationToken { - CancellationToken { _ in - self.cancelCounter.wrappingIncrement(ordering: .relaxed) - } - } + ) -> CancellationToken { CancellationToken { _ in self.cancelCounter.wrappingIncrement(ordering: .relaxed) } } func lookup(_ service: String, deadline: DispatchTime?, callback: @escaping (Result<[String], Error>) -> Void) { fatalError("TODO: Unimplemented") @@ -50,23 +45,16 @@ final class AsyncAwaitTests: XCTestCase { try await withThrowingTaskGroup(of: Void.self) { taskGroup in let snapshots = discoveryService.subscribe(to: "foo") - taskGroup.addTask { - for try await _ in snapshots { - XCTFail("Should never be reached") - } - } + taskGroup.addTask { for try await _ in snapshots { XCTFail("Should never be reached") } } XCTAssertEqual(discoveryService.cancelCounter.load(ordering: .relaxed), 0) taskGroup.cancelAll() _ = await taskGroup.nextResult() // Wait for task group to be cancelled and cancelCounter is incremented - repeat { - try await Task.sleep(nanoseconds: 10_000_000) - } while !taskGroup.isCancelled + repeat { try await Task.sleep(nanoseconds: 10_000_000) } while !taskGroup.isCancelled XCTAssertEqual(discoveryService.cancelCounter.load(ordering: .relaxed), 1) } } } -#endif diff --git a/Tests/ServiceDiscoveryTests/FilterInstanceServiceDiscoveryTests.swift b/Tests/ServiceDiscoveryTests/FilterInstanceServiceDiscoveryTests.swift index 233fd07..0a21275 100644 --- a/Tests/ServiceDiscoveryTests/FilterInstanceServiceDiscoveryTests.swift +++ b/Tests/ServiceDiscoveryTests/FilterInstanceServiceDiscoveryTests.swift @@ -22,50 +22,60 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { typealias Instance = HostPort static let fooService = "fooService" - static let fooBaseInstances = [ - HostPort(host: "localhost", port: 7001), - HostPort(host: "localhost", port: 7003), - ] - static let fooDerivedInstances = [ - HostPort(host: "localhost", port: 7001), - ] + static let fooBaseInstances = [HostPort(host: "localhost", port: 7001), HostPort(host: "localhost", port: 7003)] + static let fooDerivedInstances = [HostPort(host: "localhost", port: 7001)] static let barService = "bar-service" static let barBaseInstances = [ - HostPort(host: "localhost", port: 9001), - HostPort(host: "localhost", port: 9002), + HostPort(host: "localhost", port: 9001), HostPort(host: "localhost", port: 9002), HostPort(host: "localhost", port: 80), ] - static let barDerivedInstances = [ - HostPort(host: "localhost", port: 9001), - HostPort(host: "localhost", port: 9002), - ] + static let barDerivedInstances = [HostPort(host: "localhost", port: 9001), HostPort(host: "localhost", port: 9002)] func test_lookup() throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + var configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) configuration.register(service: Self.barService, instances: Self.barBaseInstances) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).filterInstance { [7001, 9001, 9002].contains($0.port) } + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .filterInstance { [7001, 9001, 9002].contains($0.port) } let fooResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.fooService) guard case .success(let _fooInstances) = fooResult else { return XCTFail("Failed to lookup instances for service[\(Self.fooService)]") } - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooDerivedInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooDerivedInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)" + ) let barResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.barService) guard case .success(let _barInstances) = barResult else { return XCTFail("Failed to lookup instances for service[\(Self.barService)]") } - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barDerivedInstances, "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barDerivedInstances, + "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)" + ) } func test_lookup_errorIfServiceUnknown() throws { let unknownService = "unknown-service" - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).filterInstance { $0.port == 7001 } let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: unknownService) @@ -78,7 +88,8 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { } func test_subscribe() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDiscovery.filterInstance { [7001, 9001, 9002].contains($0.port) } @@ -87,7 +98,11 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .serviceDiscoveryUnavailable, "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)") + XCTAssertEqual( + reason, + .serviceDiscoveryUnavailable, + "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -105,14 +120,24 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barDerivedInstances, "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barDerivedInstances, + "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)" + ) semaphore.signal() } }, @@ -125,7 +150,11 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter.load(ordering: .relaxed), 2, "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter.load(ordering: .relaxed), + 2, + "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `shutdown` baseServiceDiscovery.shutdown() @@ -133,7 +162,9 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { } func test_subscribe_cancel() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooBaseInstances + ]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDiscovery.filterInstance { [7001, 9001, 9002].contains($0.port) } @@ -155,14 +186,24 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter1.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barDerivedInstances, "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barDerivedInstances, + "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)" + ) semaphore.signal() } }, @@ -171,7 +212,11 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .cancellationRequested, "Expected CompletionReason to be .cancellationRequested, got \(reason)") + XCTAssertEqual( + reason, + .cancellationRequested, + "Expected CompletionReason to be .cancellationRequested, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -187,11 +232,14 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } - case .success: - return XCTFail("Does not expect to receive instances list") + case .success: return XCTFail("Does not expect to receive instances list") } }, onComplete: onComplete @@ -206,14 +254,24 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter1.load(ordering: .relaxed), 2, "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))") - XCTAssertEqual(resultCounter2.load(ordering: .relaxed), 1, "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter1.load(ordering: .relaxed), + 2, + "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))" + ) + XCTAssertEqual( + resultCounter2.load(ordering: .relaxed), + 1, + "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `cancel` XCTAssertTrue(onCompleteInvoked.load(ordering: .relaxed), "Expected onComplete to be invoked") } func test_concurrency() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooBaseInstances + ]) let baseServiceDisovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDisovery.filterInstance { $0.port == 7001 } @@ -224,41 +282,46 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { let lookupCounter = ManagedAtomic(0) let times = 100 - for _ in 1 ... times { - DispatchQueue.global().async { - baseServiceDisovery.register(Self.fooService, instances: Self.fooBaseInstances) - registerCounter.wrappingIncrement(ordering: .relaxed) + for _ in 1...times { + DispatchQueue.global() + .async { + baseServiceDisovery.register(Self.fooService, instances: Self.fooBaseInstances) + registerCounter.wrappingIncrement(ordering: .relaxed) - if registerCounter.load(ordering: .relaxed) == times { - registerSemaphore.signal() + if registerCounter.load(ordering: .relaxed) == times { registerSemaphore.signal() } } - } - DispatchQueue.global().async { - serviceDiscovery.lookup(Self.fooService, deadline: nil) { result in - lookupCounter.wrappingIncrement(ordering: .relaxed) + DispatchQueue.global() + .async { + serviceDiscovery.lookup(Self.fooService, deadline: nil) { result in + lookupCounter.wrappingIncrement(ordering: .relaxed) - guard case .success(let instances) = result, instances == Self.fooDerivedInstances else { - return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") - } + guard case .success(let instances) = result, instances == Self.fooDerivedInstances else { + return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") + } - if lookupCounter.load(ordering: .relaxed) == times { - lookupSemaphore.signal() + if lookupCounter.load(ordering: .relaxed) == times { lookupSemaphore.signal() } } } - } } _ = registerSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) _ = lookupSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) XCTAssertEqual(registerCounter.load(ordering: .relaxed), times, "Expected register to succeed \(times) times") - XCTAssertEqual(lookupCounter.load(ordering: .relaxed), times, "Expected lookup callback to be called \(times) times") + XCTAssertEqual( + lookupCounter.load(ordering: .relaxed), + times, + "Expected lookup callback to be called \(times) times" + ) } func testThrownErrorsPropagateIntoFailures() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).filterInstance { _ in throw TestError.error } + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooBaseInstances + ]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .filterInstance { _ in throw TestError.error } let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.fooService) guard case .failure(let err) = result else { @@ -269,32 +332,55 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { } func testPropagateDefaultTimeout() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).filterInstance { $0.port == 7001 } - XCTAssertTrue(compareTimeInterval(configuration.defaultLookupTimeout, serviceDiscovery.defaultLookupTimeout), "\(configuration.defaultLookupTimeout) does not match \(serviceDiscovery.defaultLookupTimeout)") + XCTAssertTrue( + compareTimeInterval(configuration.defaultLookupTimeout, serviceDiscovery.defaultLookupTimeout), + "\(configuration.defaultLookupTimeout) does not match \(serviceDiscovery.defaultLookupTimeout)" + ) } // MARK: - async/await API tests func test_async_lookup() async throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + var configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) configuration.register(service: Self.barService, instances: Self.barBaseInstances) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).filterInstance { [7001, 9001, 9002].contains($0.port) } + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .filterInstance { [7001, 9001, 9002].contains($0.port) } let _fooInstances = try await serviceDiscovery.lookup(Self.fooService) - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooDerivedInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooDerivedInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)" + ) let _barInstances = try await serviceDiscovery.lookup(Self.barService) - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barDerivedInstances, "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barDerivedInstances, + "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)" + ) } func test_async_lookup_errorIfServiceUnknown() async throws { let unknownService = "unknown-service" - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).filterInstance { $0.port == 7001 } do { @@ -308,7 +394,8 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { } func test_async_subscribe() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDiscovery.filterInstance { [7001, 9001, 9002].contains($0.port) } @@ -329,30 +416,40 @@ class FilterInstanceServiceDiscoveryTests: XCTestCase { for try await instances in serviceDiscovery.subscribe(to: Self.barService) { switch counter.wrappingIncrementThenLoad(ordering: .relaxed) { case 1: - XCTAssertEqual(instances, [], "Expected instances of \(Self.barService) to be empty, got \(instances)") + XCTAssertEqual( + instances, + [], + "Expected instances of \(Self.barService) to be empty, got \(instances)" + ) case 2: - XCTAssertEqual(instances, Self.barDerivedInstances, "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barDerivedInstances, + "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)" + ) // This causes the stream to terminate baseServiceDiscovery.shutdown() - default: - XCTFail("Expected to receive instances 2 times") + default: XCTFail("Expected to receive instances 2 times") } } } catch { switch counter.load(ordering: .relaxed) { - case 2: // shutdown is called after receiving two results - guard let serviceDiscoveryError = error as? ServiceDiscoveryError, serviceDiscoveryError == .unavailable else { - return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") - } + case 2: // shutdown is called after receiving two results + guard let serviceDiscoveryError = error as? ServiceDiscoveryError, + serviceDiscoveryError == .unavailable + else { return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") } // Test is complete at this point - default: - XCTFail("Unexpected error \(error)") + default: XCTFail("Unexpected error \(error)") } } } _ = await task.result - XCTAssertEqual(counter.load(ordering: .relaxed), 2, "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times") + XCTAssertEqual( + counter.load(ordering: .relaxed), + 2, + "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times" + ) } } diff --git a/Tests/ServiceDiscoveryTests/Helpers.swift b/Tests/ServiceDiscoveryTests/Helpers.swift index 1ec534f..b4d93dd 100644 --- a/Tests/ServiceDiscoveryTests/Helpers.swift +++ b/Tests/ServiceDiscoveryTests/Helpers.swift @@ -16,32 +16,25 @@ import Dispatch import ServiceDiscovery import XCTest -enum TestError: Error { - case error -} +enum TestError: Error { case error } func compareTimeInterval(_ lhs: DispatchTimeInterval, _ rhs: DispatchTimeInterval) -> Bool { switch (lhs, rhs) { - case (.seconds(let lhs), .seconds(let rhs)): - return lhs == rhs - case (.milliseconds(let lhs), .milliseconds(let rhs)): - return lhs == rhs - case (.microseconds(let lhs), .microseconds(let rhs)): - return lhs == rhs - case (.nanoseconds(let lhs), .nanoseconds(let rhs)): - return lhs == rhs - case (.never, .never): - return true - case (.seconds, _), (.milliseconds, _), (.microseconds, _), (.nanoseconds, _), (.never, _): - return false - #if os(macOS) || os(iOS) || os(watchOS) || os(tvOS) - @unknown default: - return false + case (.seconds(let lhs), .seconds(let rhs)): return lhs == rhs + case (.milliseconds(let lhs), .milliseconds(let rhs)): return lhs == rhs + case (.microseconds(let lhs), .microseconds(let rhs)): return lhs == rhs + case (.nanoseconds(let lhs), .nanoseconds(let rhs)): return lhs == rhs + case (.never, .never): return true + case (.seconds, _), (.milliseconds, _), (.microseconds, _), (.nanoseconds, _), (.never, _): return false + #if canImport(Darwin) + @unknown default: return false #endif } } -func ensureResult(serviceDiscovery: SD, service: SD.Service) throws -> Result<[SD.Instance], Error> { +func ensureResult(serviceDiscovery: SD, service: SD.Service) throws -> Result< + [SD.Instance], Error +> { let semaphore = DispatchSemaphore(value: 0) var result: Result<[SD.Instance], Error>? @@ -52,9 +45,7 @@ func ensureResult(serviceDiscovery: SD, service: SD.Servic _ = semaphore.wait(timeout: DispatchTime.now() + .seconds(1)) - guard let _result = result else { - throw LookupError.timedOut - } + guard let _result = result else { throw LookupError.timedOut } return _result } diff --git a/Tests/ServiceDiscoveryTests/InMemoryServiceDiscoveryTests.swift b/Tests/ServiceDiscoveryTests/InMemoryServiceDiscoveryTests.swift index 20eb6d2..1558520 100644 --- a/Tests/ServiceDiscoveryTests/InMemoryServiceDiscoveryTests.swift +++ b/Tests/ServiceDiscoveryTests/InMemoryServiceDiscoveryTests.swift @@ -22,18 +22,14 @@ class InMemoryServiceDiscoveryTests: XCTestCase { typealias Instance = HostPort static let fooService = "fooService" - static let fooInstances = [ - HostPort(host: "localhost", port: 7001), - ] + static let fooInstances = [HostPort(host: "localhost", port: 7001)] static let barService = "bar-service" - static let barInstances = [ - HostPort(host: "localhost", port: 9001), - HostPort(host: "localhost", port: 9002), - ] + static let barInstances = [HostPort(host: "localhost", port: 9001), HostPort(host: "localhost", port: 9002)] func test_lookup() throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + var configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) configuration.register(service: Self.barService, instances: Self.barInstances) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) @@ -42,21 +38,38 @@ class InMemoryServiceDiscoveryTests: XCTestCase { guard case .success(let _fooInstances) = fooResult else { return XCTFail("Failed to lookup instances for service[\(Self.fooService)]") } - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) let barResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.barService) guard case .success(let _barInstances) = barResult else { return XCTFail("Failed to lookup instances for service[\(Self.barService)]") } - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barInstances, "Expected service[\(Self.barService)] to have instances \(Self.barInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barInstances, + "Expected service[\(Self.barService)] to have instances \(Self.barInstances), got \(_barInstances)" + ) } func test_lookup_errorIfServiceUnknown() throws { let unknownService = "unknown-service" - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: unknownService) @@ -69,7 +82,8 @@ class InMemoryServiceDiscoveryTests: XCTestCase { } func test_subscribe() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let semaphore = DispatchSemaphore(value: 0) @@ -77,7 +91,11 @@ class InMemoryServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .serviceDiscoveryUnavailable, "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)") + XCTAssertEqual( + reason, + .serviceDiscoveryUnavailable, + "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -95,14 +113,24 @@ class InMemoryServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) semaphore.signal() } }, @@ -115,7 +143,11 @@ class InMemoryServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter.load(ordering: .relaxed), 2, "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter.load(ordering: .relaxed), + 2, + "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `shutdown` serviceDiscovery.shutdown() @@ -123,7 +155,8 @@ class InMemoryServiceDiscoveryTests: XCTestCase { } func test_subscribe_cancel() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let semaphore = DispatchSemaphore(value: 0) @@ -144,14 +177,24 @@ class InMemoryServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter1.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) semaphore.signal() } } @@ -159,7 +202,11 @@ class InMemoryServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .cancellationRequested, "Expected CompletionReason to be .cancellationRequested, got \(reason)") + XCTAssertEqual( + reason, + .cancellationRequested, + "Expected CompletionReason to be .cancellationRequested, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -175,11 +222,14 @@ class InMemoryServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } - case .success: - return XCTFail("Does not expect to receive instances list") + case .success: return XCTFail("Does not expect to receive instances list") } }, onComplete: onComplete @@ -194,14 +244,23 @@ class InMemoryServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter1.load(ordering: .relaxed), 2, "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))") - XCTAssertEqual(resultCounter2.load(ordering: .relaxed), 1, "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter1.load(ordering: .relaxed), + 2, + "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))" + ) + XCTAssertEqual( + resultCounter2.load(ordering: .relaxed), + 1, + "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `cancel` XCTAssertTrue(onCompleteInvoked.load(ordering: .relaxed), "Expected onComplete to be invoked") } func test_concurrency() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let registerSemaphore = DispatchSemaphore(value: 0) @@ -211,59 +270,79 @@ class InMemoryServiceDiscoveryTests: XCTestCase { let lookupCounter = ManagedAtomic(0) let times = 100 - for _ in 1 ... times { - DispatchQueue.global().async { - serviceDiscovery.register(Self.fooService, instances: Self.fooInstances) - registerCounter.wrappingIncrement(ordering: .relaxed) + for _ in 1...times { + DispatchQueue.global() + .async { + serviceDiscovery.register(Self.fooService, instances: Self.fooInstances) + registerCounter.wrappingIncrement(ordering: .relaxed) - if registerCounter.load(ordering: .relaxed) == times { - registerSemaphore.signal() + if registerCounter.load(ordering: .relaxed) == times { registerSemaphore.signal() } } - } - DispatchQueue.global().async { - serviceDiscovery.lookup(Self.fooService) { result in - lookupCounter.wrappingIncrement(ordering: .relaxed) + DispatchQueue.global() + .async { + serviceDiscovery.lookup(Self.fooService) { result in + lookupCounter.wrappingIncrement(ordering: .relaxed) - guard case .success(let instances) = result, instances == Self.fooInstances else { - return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") - } + guard case .success(let instances) = result, instances == Self.fooInstances else { + return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") + } - if lookupCounter.load(ordering: .relaxed) == times { - lookupSemaphore.signal() + if lookupCounter.load(ordering: .relaxed) == times { lookupSemaphore.signal() } } } - } } _ = registerSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) _ = lookupSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) XCTAssertEqual(registerCounter.load(ordering: .relaxed), times, "Expected register to succeed \(times) times") - XCTAssertEqual(lookupCounter.load(ordering: .relaxed), times, "Expected lookup callback to be called \(times) times") + XCTAssertEqual( + lookupCounter.load(ordering: .relaxed), + times, + "Expected lookup callback to be called \(times) times" + ) } // MARK: - async/await API tests func test_async_lookup() async throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + var configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) configuration.register(service: Self.barService, instances: Self.barInstances) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let _fooInstances = try await serviceDiscovery.lookup(Self.fooService) - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) let _barInstances = try await serviceDiscovery.lookup(Self.barService) - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barInstances, "Expected service[\(Self.barService)] to have instances \(Self.barInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barInstances, + "Expected service[\(Self.barService)] to have instances \(Self.barInstances), got \(_barInstances)" + ) } func test_async_lookup_errorIfServiceUnknown() async throws { let unknownService = "unknown-service" - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) do { @@ -277,7 +356,8 @@ class InMemoryServiceDiscoveryTests: XCTestCase { } func test_async_subscribe() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let counter = ManagedAtomic(0) @@ -297,30 +377,40 @@ class InMemoryServiceDiscoveryTests: XCTestCase { for try await instances in serviceDiscovery.subscribe(to: Self.barService) { switch counter.wrappingIncrementThenLoad(ordering: .relaxed) { case 1: - XCTAssertEqual(instances, [], "Expected instances of \(Self.barService) to be empty, got \(instances)") + XCTAssertEqual( + instances, + [], + "Expected instances of \(Self.barService) to be empty, got \(instances)" + ) case 2: - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) // This causes the stream to terminate serviceDiscovery.shutdown() - default: - XCTFail("Expected to receive instances 2 times") + default: XCTFail("Expected to receive instances 2 times") } } } catch { switch counter.load(ordering: .relaxed) { - case 2: // shutdown is called after receiving two results - guard let serviceDiscoveryError = error as? ServiceDiscoveryError, serviceDiscoveryError == .unavailable else { - return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") - } + case 2: // shutdown is called after receiving two results + guard let serviceDiscoveryError = error as? ServiceDiscoveryError, + serviceDiscoveryError == .unavailable + else { return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") } // Test is complete at this point - default: - XCTFail("Unexpected error \(error)") + default: XCTFail("Unexpected error \(error)") } } } _ = await task.result - XCTAssertEqual(counter.load(ordering: .relaxed), 2, "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times") + XCTAssertEqual( + counter.load(ordering: .relaxed), + 2, + "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times" + ) } } diff --git a/Tests/ServiceDiscoveryTests/MapInstanceServiceDiscoveryTests.swift b/Tests/ServiceDiscoveryTests/MapInstanceServiceDiscoveryTests.swift index 340703f..21cf02c 100644 --- a/Tests/ServiceDiscoveryTests/MapInstanceServiceDiscoveryTests.swift +++ b/Tests/ServiceDiscoveryTests/MapInstanceServiceDiscoveryTests.swift @@ -23,49 +23,59 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { typealias DerivedInstance = HostPort static let fooService = "fooService" - static let fooBaseInstances = [ - 7001, - ] - static let fooDerivedInstances = [ - HostPort(host: "localhost", port: 7001), - ] + static let fooBaseInstances = [7001] + static let fooDerivedInstances = [HostPort(host: "localhost", port: 7001)] static let barService = "bar-service" - static let barBaseInstances = [ - 9001, - 9002, - ] - static let barDerivedInstances = [ - HostPort(host: "localhost", port: 9001), - HostPort(host: "localhost", port: 9002), - ] + static let barBaseInstances = [9001, 9002] + static let barDerivedInstances = [HostPort(host: "localhost", port: 9001), HostPort(host: "localhost", port: 9002)] func test_lookup() throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + var configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) configuration.register(service: Self.barService, instances: Self.barBaseInstances) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapInstance { port in HostPort(host: "localhost", port: port) } + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapInstance { port in HostPort(host: "localhost", port: port) } let fooResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.fooService) guard case .success(let _fooInstances) = fooResult else { return XCTFail("Failed to lookup instances for service[\(Self.fooService)]") } - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooDerivedInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooDerivedInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)" + ) let barResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.barService) guard case .success(let _barInstances) = barResult else { return XCTFail("Failed to lookup instances for service[\(Self.barService)]") } - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barDerivedInstances, "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barDerivedInstances, + "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)" + ) } func test_lookup_errorIfServiceUnknown() throws { let unknownService = "unknown-service" - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapInstance { port in HostPort(host: "localhost", port: port) } + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapInstance { port in HostPort(host: "localhost", port: port) } let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: unknownService) guard case .failure(let error) = result else { @@ -77,7 +87,8 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { } func test_subscribe() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDiscovery.mapInstance { port in HostPort(host: "localhost", port: port) } @@ -86,7 +97,11 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .serviceDiscoveryUnavailable, "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)") + XCTAssertEqual( + reason, + .serviceDiscoveryUnavailable, + "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -104,14 +119,24 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barDerivedInstances, "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barDerivedInstances, + "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)" + ) semaphore.signal() } }, @@ -124,7 +149,11 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter.load(ordering: .relaxed), 2, "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter.load(ordering: .relaxed), + 2, + "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `shutdown` baseServiceDiscovery.shutdown() @@ -132,7 +161,9 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { } func test_subscribe_cancel() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooBaseInstances + ]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDiscovery.mapInstance { port in HostPort(host: "localhost", port: port) } @@ -154,14 +185,24 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter1.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barDerivedInstances, "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barDerivedInstances, + "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)" + ) semaphore.signal() } }, @@ -170,7 +211,11 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .cancellationRequested, "Expected CompletionReason to be .cancellationRequested, got \(reason)") + XCTAssertEqual( + reason, + .cancellationRequested, + "Expected CompletionReason to be .cancellationRequested, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -186,11 +231,14 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } - case .success: - return XCTFail("Does not expect to receive instances list") + case .success: return XCTFail("Does not expect to receive instances list") } }, onComplete: onComplete @@ -205,14 +253,24 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter1.load(ordering: .relaxed), 2, "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))") - XCTAssertEqual(resultCounter2.load(ordering: .relaxed), 1, "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter1.load(ordering: .relaxed), + 2, + "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))" + ) + XCTAssertEqual( + resultCounter2.load(ordering: .relaxed), + 1, + "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `cancel` XCTAssertTrue(onCompleteInvoked.load(ordering: .relaxed), "Expected onComplete to be invoked") } func test_concurrency() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooBaseInstances + ]) let baseServiceDisovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDisovery.mapInstance { port in HostPort(host: "localhost", port: port) } @@ -223,41 +281,46 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { let lookupCounter = ManagedAtomic(0) let times = 100 - for _ in 1 ... times { - DispatchQueue.global().async { - baseServiceDisovery.register(Self.fooService, instances: Self.fooBaseInstances) - registerCounter.wrappingIncrement(ordering: .relaxed) + for _ in 1...times { + DispatchQueue.global() + .async { + baseServiceDisovery.register(Self.fooService, instances: Self.fooBaseInstances) + registerCounter.wrappingIncrement(ordering: .relaxed) - if registerCounter.load(ordering: .relaxed) == times { - registerSemaphore.signal() + if registerCounter.load(ordering: .relaxed) == times { registerSemaphore.signal() } } - } - DispatchQueue.global().async { - serviceDiscovery.lookup(Self.fooService, deadline: nil) { result in - lookupCounter.wrappingIncrement(ordering: .relaxed) + DispatchQueue.global() + .async { + serviceDiscovery.lookup(Self.fooService, deadline: nil) { result in + lookupCounter.wrappingIncrement(ordering: .relaxed) - guard case .success(let instances) = result, instances == Self.fooDerivedInstances else { - return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") - } + guard case .success(let instances) = result, instances == Self.fooDerivedInstances else { + return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") + } - if lookupCounter.load(ordering: .relaxed) == times { - lookupSemaphore.signal() + if lookupCounter.load(ordering: .relaxed) == times { lookupSemaphore.signal() } } } - } } _ = registerSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) _ = lookupSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) XCTAssertEqual(registerCounter.load(ordering: .relaxed), times, "Expected register to succeed \(times) times") - XCTAssertEqual(lookupCounter.load(ordering: .relaxed), times, "Expected lookup callback to be called \(times) times") + XCTAssertEqual( + lookupCounter.load(ordering: .relaxed), + times, + "Expected lookup callback to be called \(times) times" + ) } func testThrownErrorsPropagateIntoFailures() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapInstance { _ -> Int in throw TestError.error } + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooBaseInstances + ]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapInstance { _ -> Int in throw TestError.error } let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.fooService) guard case .failure(let err) = result else { @@ -268,33 +331,58 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { } func testPropagateDefaultTimeout() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapInstance { port in HostPort(host: "localhost", port: port) } - XCTAssertTrue(compareTimeInterval(configuration.defaultLookupTimeout, serviceDiscovery.defaultLookupTimeout), "\(configuration.defaultLookupTimeout) does not match \(serviceDiscovery.defaultLookupTimeout)") + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapInstance { port in HostPort(host: "localhost", port: port) } + XCTAssertTrue( + compareTimeInterval(configuration.defaultLookupTimeout, serviceDiscovery.defaultLookupTimeout), + "\(configuration.defaultLookupTimeout) does not match \(serviceDiscovery.defaultLookupTimeout)" + ) } // MARK: - async/await API tests func test_async_lookup() async throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + var configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) configuration.register(service: Self.barService, instances: Self.barBaseInstances) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapInstance { port in HostPort(host: "localhost", port: port) } + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapInstance { port in HostPort(host: "localhost", port: port) } let _fooInstances = try await serviceDiscovery.lookup(Self.fooService) - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooDerivedInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooDerivedInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooDerivedInstances), got \(_fooInstances)" + ) let _barInstances = try await serviceDiscovery.lookup(Self.barService) - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barDerivedInstances, "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.barService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barDerivedInstances, + "Expected service[\(Self.barService)] to have instances \(Self.barDerivedInstances), got \(_barInstances)" + ) } func test_async_lookup_errorIfServiceUnknown() async throws { let unknownService = "unknown-service" - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapInstance { port in HostPort(host: "localhost", port: port) } + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapInstance { port in HostPort(host: "localhost", port: port) } do { _ = try await serviceDiscovery.lookup(unknownService) @@ -307,7 +395,8 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { } func test_async_subscribe() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooBaseInstances]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let serviceDiscovery = baseServiceDiscovery.mapInstance { port in HostPort(host: "localhost", port: port) } @@ -328,30 +417,40 @@ class MapInstanceServiceDiscoveryTests: XCTestCase { for try await instances in serviceDiscovery.subscribe(to: Self.barService) { switch counter.wrappingIncrementThenLoad(ordering: .relaxed) { case 1: - XCTAssertEqual(instances, [], "Expected instances of \(Self.barService) to be empty, got \(instances)") + XCTAssertEqual( + instances, + [], + "Expected instances of \(Self.barService) to be empty, got \(instances)" + ) case 2: - XCTAssertEqual(instances, Self.barDerivedInstances, "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barDerivedInstances, + "Expected instances of \(Self.barService) to be \(Self.barDerivedInstances), got \(instances)" + ) // This causes the stream to terminate baseServiceDiscovery.shutdown() - default: - XCTFail("Expected to receive instances 2 times") + default: XCTFail("Expected to receive instances 2 times") } } } catch { switch counter.load(ordering: .relaxed) { - case 2: // shutdown is called after receiving two results - guard let serviceDiscoveryError = error as? ServiceDiscoveryError, serviceDiscoveryError == .unavailable else { - return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") - } + case 2: // shutdown is called after receiving two results + guard let serviceDiscoveryError = error as? ServiceDiscoveryError, + serviceDiscoveryError == .unavailable + else { return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") } // Test is complete at this point - default: - XCTFail("Unexpected error \(error)") + default: XCTFail("Unexpected error \(error)") } } } _ = await task.result - XCTAssertEqual(counter.load(ordering: .relaxed), 2, "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times") + XCTAssertEqual( + counter.load(ordering: .relaxed), + 2, + "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times" + ) } } diff --git a/Tests/ServiceDiscoveryTests/MapServiceServiceDiscoveryTests.swift b/Tests/ServiceDiscoveryTests/MapServiceServiceDiscoveryTests.swift index 5cf5159..01e07dd 100644 --- a/Tests/ServiceDiscoveryTests/MapServiceServiceDiscoveryTests.swift +++ b/Tests/ServiceDiscoveryTests/MapServiceServiceDiscoveryTests.swift @@ -26,44 +26,60 @@ class MapServiceServiceDiscoveryTests: XCTestCase { static let computedFooService = 0 static let fooService = "fooService" - static let fooInstances = [ - HostPort(host: "localhost", port: 7001), - ] + static let fooInstances = [HostPort(host: "localhost", port: 7001)] static let computedBarService = 1 static let barService = "bar-service" - static let barInstances = [ - HostPort(host: "localhost", port: 9001), - HostPort(host: "localhost", port: 9002), - ] + static let barInstances = [HostPort(host: "localhost", port: 9001), HostPort(host: "localhost", port: 9002)] func test_lookup() throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) configuration.register(service: Self.barService, instances: Self.barInstances) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService { (service: Int) in Self.services[service] } + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService { (service: Int) in Self.services[service] } let fooResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.computedFooService) guard case .success(let _fooInstances) = fooResult else { return XCTFail("Failed to lookup instances for service[\(Self.computedFooService)]") } - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.computedFooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.computedFooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.computedFooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.computedFooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) let barResult = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.computedBarService) guard case .success(let _barInstances) = barResult else { return XCTFail("Failed to lookup instances for service[\(Self.computedBarService)]") } - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.computedBarService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barInstances, "Expected service[\(Self.computedBarService)] to have instances \(Self.barInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.computedBarService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barInstances, + "Expected service[\(Self.computedBarService)] to have instances \(Self.barInstances), got \(_barInstances)" + ) } func test_lookup_errorIfServiceUnknown() throws { let unknownService = "unknown-service" let unknownComputedService = 3 - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService(serviceType: Int.self) { _ in unknownService } + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService(serviceType: Int.self) { _ in unknownService } let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: unknownComputedService) guard case .failure(let error) = result else { @@ -75,16 +91,23 @@ class MapServiceServiceDiscoveryTests: XCTestCase { } func test_subscribe() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) - let serviceDiscovery = baseServiceDiscovery.mapService(serviceType: Int.self) { service in Self.services[service] } + let serviceDiscovery = baseServiceDiscovery.mapService(serviceType: Int.self) { service in + Self.services[service] + } let semaphore = DispatchSemaphore(value: 0) let resultCounter = ManagedAtomic(0) let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .serviceDiscoveryUnavailable, "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)") + XCTAssertEqual( + reason, + .serviceDiscoveryUnavailable, + "Expected CompletionReason to be .serviceDiscoveryUnavailable, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -102,14 +125,24 @@ class MapServiceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.computedBarService) is not registered, got \(error)") + guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.computedBarService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.computedBarService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.computedBarService) to be \(Self.barInstances), got \(instances)" + ) semaphore.signal() } }, @@ -122,7 +155,11 @@ class MapServiceServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter.load(ordering: .relaxed), 2, "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter.load(ordering: .relaxed), + 2, + "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `shutdown` baseServiceDiscovery.shutdown() @@ -130,9 +167,13 @@ class MapServiceServiceDiscoveryTests: XCTestCase { } func test_subscribe_cancel() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) - let serviceDiscovery = baseServiceDiscovery.mapService(serviceType: Int.self) { service in Self.services[service] } + let serviceDiscovery = baseServiceDiscovery.mapService(serviceType: Int.self) { service in + Self.services[service] + } let semaphore = DispatchSemaphore(value: 0) let resultCounter1 = ManagedAtomic(0) @@ -152,14 +193,24 @@ class MapServiceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.computedBarService) is not registered, got \(error)") + guard resultCounter1.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.computedBarService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter1.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter1.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.computedBarService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.computedBarService) to be \(Self.barInstances), got \(instances)" + ) semaphore.signal() } }, @@ -168,7 +219,11 @@ class MapServiceServiceDiscoveryTests: XCTestCase { let onCompleteInvoked = ManagedAtomic(false) let onComplete: (CompletionReason) -> Void = { reason in - XCTAssertEqual(reason, .cancellationRequested, "Expected CompletionReason to be .cancellationRequested, got \(reason)") + XCTAssertEqual( + reason, + .cancellationRequested, + "Expected CompletionReason to be .cancellationRequested, got \(reason)" + ) onCompleteInvoked.store(true, ordering: .relaxed) } @@ -184,11 +239,14 @@ class MapServiceServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.computedBarService) is not registered, got \(error)") + guard resultCounter2.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.computedBarService) is not registered, got \(error)" + ) } - case .success: - return XCTFail("Does not expect to receive instances list") + case .success: return XCTFail("Does not expect to receive instances list") } }, onComplete: onComplete @@ -203,16 +261,27 @@ class MapServiceServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter1.load(ordering: .relaxed), 2, "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))") - XCTAssertEqual(resultCounter2.load(ordering: .relaxed), 1, "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter1.load(ordering: .relaxed), + 2, + "Expected subscriber #1 to receive result 2 times, got \(resultCounter1.load(ordering: .relaxed))" + ) + XCTAssertEqual( + resultCounter2.load(ordering: .relaxed), + 1, + "Expected subscriber #2 to receive result 1 time, got \(resultCounter2.load(ordering: .relaxed))" + ) // Verify `onComplete` gets invoked on `cancel` XCTAssertTrue(onCompleteInvoked.load(ordering: .relaxed), "Expected onComplete to be invoked") } func test_concurrency() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) let baseServiceDisovery = InMemoryServiceDiscovery(configuration: configuration) - let serviceDiscovery = baseServiceDisovery.mapService(serviceType: Int.self) { service in Self.services[service] } + let serviceDiscovery = baseServiceDisovery.mapService(serviceType: Int.self) { service in Self.services[service] + } let registerSemaphore = DispatchSemaphore(value: 0) let registerCounter = ManagedAtomic(0) @@ -221,41 +290,46 @@ class MapServiceServiceDiscoveryTests: XCTestCase { let lookupCounter = ManagedAtomic(0) let times = 100 - for _ in 1 ... times { - DispatchQueue.global().async { - baseServiceDisovery.register(Self.fooService, instances: Self.fooInstances) - registerCounter.wrappingIncrement(ordering: .relaxed) + for _ in 1...times { + DispatchQueue.global() + .async { + baseServiceDisovery.register(Self.fooService, instances: Self.fooInstances) + registerCounter.wrappingIncrement(ordering: .relaxed) - if registerCounter.load(ordering: .relaxed) == times { - registerSemaphore.signal() + if registerCounter.load(ordering: .relaxed) == times { registerSemaphore.signal() } } - } - DispatchQueue.global().async { - serviceDiscovery.lookup(Self.computedFooService, deadline: nil) { result in - lookupCounter.wrappingIncrement(ordering: .relaxed) + DispatchQueue.global() + .async { + serviceDiscovery.lookup(Self.computedFooService, deadline: nil) { result in + lookupCounter.wrappingIncrement(ordering: .relaxed) - guard case .success(let instances) = result, instances == Self.fooInstances else { - return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") - } + guard case .success(let instances) = result, instances == Self.fooInstances else { + return XCTFail("Failed to lookup instances for service[\(Self.fooService)]: \(result)") + } - if lookupCounter.load(ordering: .relaxed) == times { - lookupSemaphore.signal() + if lookupCounter.load(ordering: .relaxed) == times { lookupSemaphore.signal() } } } - } } _ = registerSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) _ = lookupSemaphore.wait(timeout: DispatchTime.now() + .seconds(1)) XCTAssertEqual(registerCounter.load(ordering: .relaxed), times, "Expected register to succeed \(times) times") - XCTAssertEqual(lookupCounter.load(ordering: .relaxed), times, "Expected lookup callback to be called \(times) times") + XCTAssertEqual( + lookupCounter.load(ordering: .relaxed), + times, + "Expected lookup callback to be called \(times) times" + ) } func testThrownErrorsPropagateIntoFailures() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService { (_: Int) -> String in throw TestError.error } + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService { (_: Int) -> String in throw TestError.error } let result = try ensureResult(serviceDiscovery: serviceDiscovery, service: Self.computedFooService) guard case .failure(let err) = result else { @@ -266,8 +340,11 @@ class MapServiceServiceDiscoveryTests: XCTestCase { } func testThrownErrorsPropagateIntoCancelledSubscriptions() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService { (_: Int) -> String in throw TestError.error } + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService { (_: Int) -> String in throw TestError.error } let resultGroup = DispatchGroup() resultGroup.enter() @@ -276,9 +353,7 @@ class MapServiceServiceDiscoveryTests: XCTestCase { let token = serviceDiscovery.subscribe( to: Self.computedFooService, onNext: { result in - defer { - resultGroup.leave() - } + defer { resultGroup.leave() } guard case .failure(let err) = result else { XCTFail("Expected error, got \(result)") return @@ -286,9 +361,7 @@ class MapServiceServiceDiscoveryTests: XCTestCase { XCTAssertEqual(err as? TestError, .error) }, onComplete: { reason in - defer { - resultGroup.leave() - } + defer { resultGroup.leave() } XCTAssertEqual(reason, .failedToMapService) } ) @@ -296,34 +369,60 @@ class MapServiceServiceDiscoveryTests: XCTestCase { } func testPropagateDefaultTimeout() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService(serviceType: Int.self) { service in Self.services[service] } - XCTAssertTrue(compareTimeInterval(configuration.defaultLookupTimeout, serviceDiscovery.defaultLookupTimeout), "\(configuration.defaultLookupTimeout) does not match \(serviceDiscovery.defaultLookupTimeout)") + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService(serviceType: Int.self) { service in Self.services[service] } + XCTAssertTrue( + compareTimeInterval(configuration.defaultLookupTimeout, serviceDiscovery.defaultLookupTimeout), + "\(configuration.defaultLookupTimeout) does not match \(serviceDiscovery.defaultLookupTimeout)" + ) } // MARK: - async/await API tests func test_async_lookup() async throws { - var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + var configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) configuration.register(service: Self.barService, instances: Self.barInstances) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService { (service: Int) in Self.services[service] } + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService { (service: Int) in Self.services[service] } let _fooInstances = try await serviceDiscovery.lookup(Self.computedFooService) - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.computedFooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.computedFooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.computedFooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.computedFooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) let _barInstances = try await serviceDiscovery.lookup(Self.computedBarService) - XCTAssertEqual(_barInstances.count, 2, "Expected service[\(Self.computedBarService)] to have 2 instances, got \(_barInstances.count)") - XCTAssertEqual(_barInstances, Self.barInstances, "Expected service[\(Self.computedBarService)] to have instances \(Self.barInstances), got \(_barInstances)") + XCTAssertEqual( + _barInstances.count, + 2, + "Expected service[\(Self.computedBarService)] to have 2 instances, got \(_barInstances.count)" + ) + XCTAssertEqual( + _barInstances, + Self.barInstances, + "Expected service[\(Self.computedBarService)] to have instances \(Self.barInstances), got \(_barInstances)" + ) } func test_async_lookup_errorIfServiceUnknown() async throws { let unknownService = "unknown-service" let unknownComputedService = 3 - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: ["foo-service": []]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService(serviceType: Int.self) { _ in unknownService } + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: ["foo-service": []]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService(serviceType: Int.self) { _ in unknownService } do { _ = try await serviceDiscovery.lookup(unknownComputedService) @@ -336,9 +435,12 @@ class MapServiceServiceDiscoveryTests: XCTestCase { } func test_async_subscribe() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let baseServiceDiscovery = InMemoryServiceDiscovery(configuration: configuration) - let serviceDiscovery = baseServiceDiscovery.mapService(serviceType: Int.self) { service in Self.services[service] } + let serviceDiscovery = baseServiceDiscovery.mapService(serviceType: Int.self) { service in + Self.services[service] + } let counter = ManagedAtomic(0) @@ -357,36 +459,49 @@ class MapServiceServiceDiscoveryTests: XCTestCase { for try await instances in serviceDiscovery.subscribe(to: Self.computedBarService) { switch counter.wrappingIncrementThenLoad(ordering: .relaxed) { case 1: - XCTAssertEqual(instances, [], "Expected instances of \(Self.computedBarService) to be empty, got \(instances)") + XCTAssertEqual( + instances, + [], + "Expected instances of \(Self.computedBarService) to be empty, got \(instances)" + ) case 2: - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.computedBarService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.computedBarService) to be \(Self.barInstances), got \(instances)" + ) // This causes the stream to terminate baseServiceDiscovery.shutdown() - default: - XCTFail("Expected to receive instances 2 times") + default: XCTFail("Expected to receive instances 2 times") } } } catch { switch counter.load(ordering: .relaxed) { - case 2: // shutdown is called after receiving two results - guard let serviceDiscoveryError = error as? ServiceDiscoveryError, serviceDiscoveryError == .unavailable else { - return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") - } + case 2: // shutdown is called after receiving two results + guard let serviceDiscoveryError = error as? ServiceDiscoveryError, + serviceDiscoveryError == .unavailable + else { return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") } // Test is complete at this point - default: - XCTFail("Unexpected error \(error)") + default: XCTFail("Unexpected error \(error)") } } } _ = await task.result - XCTAssertEqual(counter.load(ordering: .relaxed), 2, "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times") + XCTAssertEqual( + counter.load(ordering: .relaxed), + 2, + "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times" + ) } func testThrownErrorsPropagateIntoAsyncSubscriptions() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) - let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration).mapService { (_: Int) -> String in throw TestError.error } + let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [ + Self.fooService: Self.fooInstances + ]) + let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) + .mapService { (_: Int) -> String in throw TestError.error } let task = Task { () in do { diff --git a/Tests/ServiceDiscoveryTests/TypeErasedServiceDiscoveryTests.swift b/Tests/ServiceDiscoveryTests/TypeErasedServiceDiscoveryTests.swift index 09ab6cf..3600537 100644 --- a/Tests/ServiceDiscoveryTests/TypeErasedServiceDiscoveryTests.swift +++ b/Tests/ServiceDiscoveryTests/TypeErasedServiceDiscoveryTests.swift @@ -22,18 +22,14 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { typealias Instance = HostPort static let fooService = "fooService" - static let fooInstances = [ - HostPort(host: "localhost", port: 7001), - ] + static let fooInstances = [HostPort(host: "localhost", port: 7001)] static let barService = "bar-service" - static let barInstances = [ - HostPort(host: "localhost", port: 9001), - HostPort(host: "localhost", port: 9002), - ] + static let barInstances = [HostPort(host: "localhost", port: 9001), HostPort(host: "localhost", port: 9002)] func test_ServiceDiscoveryBox_lookup() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let boxedServiceDiscovery = ServiceDiscoveryBox(serviceDiscovery) @@ -43,8 +39,16 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { guard case .success(let _fooInstances) = fooResult else { return XCTFail("Failed to lookup instances for service[\(Self.fooService)]") } - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) semaphore.signal() } @@ -55,7 +59,8 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { } func test_ServiceDiscoveryBox_subscribe() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let boxedServiceDiscovery = ServiceDiscoveryBox(serviceDiscovery) @@ -76,14 +81,24 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) semaphore.signal() } } @@ -95,11 +110,16 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter.load(ordering: .relaxed), 2, "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter.load(ordering: .relaxed), + 2, + "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))" + ) } func test_ServiceDiscoveryBox_unwrap() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let boxedServiceDiscovery = ServiceDiscoveryBox(serviceDiscovery) @@ -107,7 +127,8 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { } func test_AnyServiceDiscovery_lookup() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let anyServiceDiscovery = AnyServiceDiscovery(serviceDiscovery) @@ -117,8 +138,16 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { guard case .success(let _fooInstances) = fooResult else { return XCTFail("Failed to lookup instances for service[\(Self.fooService)]") } - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) semaphore.signal() } @@ -129,7 +158,8 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { } func test_AnyServiceDiscovery_subscribe() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let anyServiceDiscovery = AnyServiceDiscovery(serviceDiscovery) @@ -150,14 +180,24 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { switch result { case .failure(let error): - guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, case .unknownService = lookupError else { - return XCTFail("Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)") + guard resultCounter.load(ordering: .relaxed) == 1, let lookupError = error as? LookupError, + case .unknownService = lookupError + else { + return XCTFail( + "Expected the first result to be LookupError.unknownService since \(Self.barService) is not registered, got \(error)" + ) } case .success(let instances): guard resultCounter.load(ordering: .relaxed) == 2 else { - return XCTFail("Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)") + return XCTFail( + "Expected to receive instances list on the second result only, but at result #\(resultCounter.load(ordering: .relaxed)) got \(instances)" + ) } - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) semaphore.signal() } } @@ -169,11 +209,16 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { _ = semaphore.wait(timeout: DispatchTime.now() + .milliseconds(200)) - XCTAssertEqual(resultCounter.load(ordering: .relaxed), 2, "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))") + XCTAssertEqual( + resultCounter.load(ordering: .relaxed), + 2, + "Expected to receive result 2 times, got \(resultCounter.load(ordering: .relaxed))" + ) } func test_AnyServiceDiscovery_unwrap() throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let anyServiceDiscovery = AnyServiceDiscovery(serviceDiscovery) @@ -183,17 +228,27 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { // MARK: - async/await API tests func test_ServiceDiscoveryBox_async_lookup() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let boxedServiceDiscovery = ServiceDiscoveryBox(serviceDiscovery) let _fooInstances = try await boxedServiceDiscovery.lookup(Self.fooService) - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) } func test_ServiceDiscoveryBox_async_subscribe() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let boxedServiceDiscovery = ServiceDiscoveryBox(serviceDiscovery) @@ -214,45 +269,65 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { for try await instances in boxedServiceDiscovery.subscribe(to: Self.barService) { switch counter.wrappingIncrementThenLoad(ordering: .relaxed) { case 1: - XCTAssertEqual(instances, [], "Expected instances of \(Self.barService) to be empty, got \(instances)") + XCTAssertEqual( + instances, + [], + "Expected instances of \(Self.barService) to be empty, got \(instances)" + ) case 2: - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) // This causes the stream to terminate serviceDiscovery.shutdown() - default: - XCTFail("Expected to receive instances 2 times") + default: XCTFail("Expected to receive instances 2 times") } } } catch { switch counter.load(ordering: .relaxed) { - case 2: // shutdown is called after receiving two results - guard let serviceDiscoveryError = error as? ServiceDiscoveryError, serviceDiscoveryError == .unavailable else { - return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") - } + case 2: // shutdown is called after receiving two results + guard let serviceDiscoveryError = error as? ServiceDiscoveryError, + serviceDiscoveryError == .unavailable + else { return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") } // Test is complete at this point - default: - XCTFail("Unexpected error \(error)") + default: XCTFail("Unexpected error \(error)") } } } _ = await task.result - XCTAssertEqual(counter.load(ordering: .relaxed), 2, "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times") + XCTAssertEqual( + counter.load(ordering: .relaxed), + 2, + "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times" + ) } func test_AnyServiceDiscovery_async_lookup() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let anyServiceDiscovery = AnyServiceDiscovery(serviceDiscovery) let _fooInstances: [Instance] = try await anyServiceDiscovery.lookupAndUnwrap(Self.fooService) - XCTAssertEqual(_fooInstances.count, 1, "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)") - XCTAssertEqual(_fooInstances, Self.fooInstances, "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)") + XCTAssertEqual( + _fooInstances.count, + 1, + "Expected service[\(Self.fooService)] to have 1 instance, got \(_fooInstances.count)" + ) + XCTAssertEqual( + _fooInstances, + Self.fooInstances, + "Expected service[\(Self.fooService)] to have instances \(Self.fooInstances), got \(_fooInstances)" + ) } func test_AnyServiceDiscovery_async_subscribe() async throws { - let configuration = InMemoryServiceDiscovery.Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) + let configuration = InMemoryServiceDiscovery + .Configuration(serviceInstances: [Self.fooService: Self.fooInstances]) let serviceDiscovery = InMemoryServiceDiscovery(configuration: configuration) let anyServiceDiscovery = AnyServiceDiscovery(serviceDiscovery) @@ -273,30 +348,40 @@ class TypeErasedServiceDiscoveryTests: XCTestCase { for try await instances: [Instance] in anyServiceDiscovery.subscribeAndUnwrap(to: Self.barService) { switch counter.wrappingIncrementThenLoad(ordering: .relaxed) { case 1: - XCTAssertEqual(instances, [], "Expected instances of \(Self.barService) to be empty, got \(instances)") + XCTAssertEqual( + instances, + [], + "Expected instances of \(Self.barService) to be empty, got \(instances)" + ) case 2: - XCTAssertEqual(instances, Self.barInstances, "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)") + XCTAssertEqual( + instances, + Self.barInstances, + "Expected instances of \(Self.barService) to be \(Self.barInstances), got \(instances)" + ) // This causes the stream to terminate serviceDiscovery.shutdown() - default: - XCTFail("Expected to receive instances 2 times") + default: XCTFail("Expected to receive instances 2 times") } } } catch { switch counter.load(ordering: .relaxed) { - case 2: // shutdown is called after receiving two results - guard let serviceDiscoveryError = error as? ServiceDiscoveryError, serviceDiscoveryError == .unavailable else { - return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") - } + case 2: // shutdown is called after receiving two results + guard let serviceDiscoveryError = error as? ServiceDiscoveryError, + serviceDiscoveryError == .unavailable + else { return XCTFail("Expected ServiceDiscoveryError.unavailable, got \(error)") } // Test is complete at this point - default: - XCTFail("Unexpected error \(error)") + default: XCTFail("Unexpected error \(error)") } } } _ = await task.result - XCTAssertEqual(counter.load(ordering: .relaxed), 2, "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times") + XCTAssertEqual( + counter.load(ordering: .relaxed), + 2, + "Expected to receive instances 2 times, got \(counter.load(ordering: .relaxed)) times" + ) } } diff --git a/docker/Dockerfile b/docker/Dockerfile deleted file mode 100644 index e146347..0000000 --- a/docker/Dockerfile +++ /dev/null @@ -1,28 +0,0 @@ -ARG swift_version=5.6 -ARG ubuntu_version=focal -ARG base_image=swift:$swift_version-$ubuntu_version -FROM $base_image -# needed to do again after FROM due to docker limitation -ARG swift_version -ARG ubuntu_version - -# set as UTF-8 -RUN apt-get update && apt-get install -y locales locales-all -ENV LC_ALL en_US.UTF-8 -ENV LANG en_US.UTF-8 -ENV LANGUAGE en_US.UTF-8 - -# ruby and jazzy for docs generation -RUN apt-get update && apt-get install -y ruby ruby-dev libsqlite3-dev -RUN if [ "${ubuntu_version}" == "focal" ] ; then gem install jazzy --no-document ; fi - -# tools -RUN mkdir -p $HOME/.tools -RUN echo 'export PATH="$HOME/.tools:$PATH"' >> $HOME/.profile - -# swiftformat (until part of the toolchain) - -ARG swiftformat_version=0.48.11 -RUN git clone --branch $swiftformat_version --depth 1 https://github.com/nicklockwood/SwiftFormat $HOME/.tools/swift-format -RUN cd $HOME/.tools/swift-format && swift build -c release -RUN ln -s $HOME/.tools/swift-format/.build/release/swiftformat $HOME/.tools/swiftformat diff --git a/docker/docker-compose.2004.56.yaml b/docker/docker-compose.2004.56.yaml deleted file mode 100644 index 8650830..0000000 --- a/docker/docker-compose.2004.56.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:20.04-5.6 - build: - args: - ubuntu_version: "focal" - swift_version: "5.6" - - test: - image: swift-service-discovery:20.04-5.6 - environment: [] - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: swift-service-discovery:20.04-5.6 diff --git a/docker/docker-compose.2004.57.yaml b/docker/docker-compose.2004.57.yaml deleted file mode 100644 index 1ba37ed..0000000 --- a/docker/docker-compose.2004.57.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:20.04-5.7 - build: - args: - ubuntu_version: "focal" - swift_version: "5.7" - - test: - image: swift-service-discovery:20.04-5.7 - environment: [] - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: swift-service-discovery:20.04-5.7 diff --git a/docker/docker-compose.2004.58.yaml b/docker/docker-compose.2004.58.yaml deleted file mode 100644 index 610914d..0000000 --- a/docker/docker-compose.2004.58.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:20.04-5.8 - build: - args: - ubuntu_version: "focal" - swift_version: "5.8" - - test: - image: swift-service-discovery:20.04-5.8 - environment: [] - #- SANITIZER_ARG=--sanitize=thread - - shell: - image: swift-service-discovery:20.04-5.8 diff --git a/docker/docker-compose.2204.510.yaml b/docker/docker-compose.2204.510.yaml deleted file mode 100644 index 06a7524..0000000 --- a/docker/docker-compose.2204.510.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:22.04-5.10 - build: - args: - base_image: "swiftlang/swift:nightly-5.10-jammy" - - test: - image: swift-service-discovery:22.04-5.10 - environment: [] - #- SANITIZER_ARG=--sanitize=thread - command: /bin/bash -xcl "swift test $${SANITIZER_ARG-}" # Disable -warnings-as-errors due to Sendable warnings - - shell: - image: swift-service-discovery:22.04-5.10 diff --git a/docker/docker-compose.2204.59.yaml b/docker/docker-compose.2204.59.yaml deleted file mode 100644 index ce086b5..0000000 --- a/docker/docker-compose.2204.59.yaml +++ /dev/null @@ -1,19 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:22.04-5.9 - build: - args: - ubuntu_version: "jammy" - swift_version: "5.9" - - test: - image: swift-service-discovery:22.04-5.9 - environment: [] - #- SANITIZER_ARG=--sanitize=thread - command: /bin/bash -xcl "swift test $${SANITIZER_ARG-}" # Disable -warnings-as-errors due to Sendable warnings - - shell: - image: swift-service-discovery:22.04-5.9 diff --git a/docker/docker-compose.2204.main.yaml b/docker/docker-compose.2204.main.yaml deleted file mode 100644 index a4e2135..0000000 --- a/docker/docker-compose.2204.main.yaml +++ /dev/null @@ -1,18 +0,0 @@ -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:22.04-main - build: - args: - base_image: "swiftlang/swift:nightly-main-jammy" - - test: - image: swift-service-discovery:22.04-main - environment: [] - #- SANITIZER_ARG=--sanitize=thread - command: /bin/bash -xcl "swift test $${SANITIZER_ARG-}" # Disable -warnings-as-errors due to Sendable warnings - - shell: - image: swift-service-discovery:22.04-main diff --git a/docker/docker-compose.yaml b/docker/docker-compose.yaml deleted file mode 100644 index fec157b..0000000 --- a/docker/docker-compose.yaml +++ /dev/null @@ -1,37 +0,0 @@ -# this file is not designed to be run directly -# instead, use the docker-compose.. files -# eg docker-compose -f docker/docker-compose.yaml -f docker/docker-compose.1804.51.yaml run test -version: "3" - -services: - - runtime-setup: - image: swift-service-discovery:default - build: - context: . - dockerfile: Dockerfile - - common: &common - image: swift-service-discovery:default - depends_on: [runtime-setup] - volumes: - - ~/.ssh:/root/.ssh - - ..:/code:z - working_dir: /code - cap_drop: - - CAP_NET_RAW - - CAP_NET_BIND_SERVICE - - soundness: - <<: *common - command: /bin/bash -xcl "./scripts/soundness.sh" - - test: - <<: *common - command: /bin/bash -xcl "swift test -Xswiftc -warnings-as-errors $${SANITIZER_ARG-}" - - # util - - shell: - <<: *common - entrypoint: /bin/bash diff --git a/scripts/check_no_api_breakages.sh b/scripts/check_no_api_breakages.sh deleted file mode 100755 index f63de61..0000000 --- a/scripts/check_no_api_breakages.sh +++ /dev/null @@ -1,136 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceDiscovery open source project -## -## Copyright (c) 2020 Apple Inc. and the SwiftServiceDiscovery project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftNIO open source project -## -## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftNIO project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu - -# repodir -function all_modules() { - local repodir="$1" - ( - set -eu - cd "$repodir" - swift package dump-package | jq '.products | - map(select(.type | has("library") )) | - map(.name) | .[]' | tr -d '"' - ) -} - -# repodir tag output -function build_and_do() { - local repodir=$1 - local tag=$2 - local output=$3 - - ( - cd "$repodir" - git checkout -q "$tag" - swift build - while read -r module; do - swift api-digester -sdk "$sdk" -dump-sdk -module "$module" \ - -o "$output/$module.json" -I "$repodir/.build/debug" - done < <(all_modules "$repodir") - ) -} - -function usage() { - echo >&2 "Usage: $0 REPO-GITHUB-URL NEW-VERSION OLD-VERSIONS..." - echo >&2 - echo >&2 "This script requires a Swift 5.1+ toolchain." - echo >&2 - echo >&2 "Examples:" - echo >&2 - echo >&2 "Check between master and tag 2.1.1 of swift-nio:" - echo >&2 " $0 https://github.com/apple/swift-nio master 2.1.1" - echo >&2 - echo >&2 "Check between HEAD and commit 64cf63d7 using the provided toolchain:" - echo >&2 " xcrun --toolchain org.swift.5120190702a $0 ../some-local-repo HEAD 64cf63d7" -} - -if [[ $# -lt 3 ]]; then - usage - exit 1 -fi - -sdk=/ -if [[ "$(uname -s)" == Darwin ]]; then - sdk=$(xcrun --show-sdk-path) -fi - -hash jq 2> /dev/null || { echo >&2 "ERROR: jq must be installed"; exit 1; } -tmpdir=$(mktemp -d /tmp/.check-api_XXXXXX) -repo_url=$1 -new_tag=$2 -shift 2 - -repodir="$tmpdir/repo" -git clone "$repo_url" "$repodir" -git -C "$repodir" fetch -q origin '+refs/pull/*:refs/remotes/origin/pr/*' -errors=0 - -for old_tag in "$@"; do - mkdir "$tmpdir/api-old" - mkdir "$tmpdir/api-new" - - echo "Checking public API breakages from $old_tag to $new_tag" - - build_and_do "$repodir" "$new_tag" "$tmpdir/api-new/" - build_and_do "$repodir" "$old_tag" "$tmpdir/api-old/" - - for f in "$tmpdir/api-new"/*; do - f=$(basename "$f") - report="$tmpdir/$f.report" - if [[ ! -f "$tmpdir/api-old/$f" ]]; then - echo "NOTICE: NEW MODULE $f" - continue - fi - - echo -n "Checking $f... " - swift api-digester -sdk "$sdk" -diagnose-sdk \ - --input-paths "$tmpdir/api-old/$f" -input-paths "$tmpdir/api-new/$f" 2>&1 \ - > "$report" 2>&1 - - if ! shasum "$report" | grep -q cefc4ee5bb7bcdb7cb5a7747efa178dab3c794d5; then - echo ERROR - echo >&2 "==============================" - echo >&2 "ERROR: public API change in $f" - echo >&2 "==============================" - cat >&2 "$report" - errors=$(( errors + 1 )) - else - echo OK - fi - done - rm -rf "$tmpdir/api-new" "$tmpdir/api-old" -done - -if [[ "$errors" == 0 ]]; then - echo "OK, all seems good" -fi -echo done -exit "$errors" diff --git a/scripts/generate_contributors_list.sh b/scripts/generate_contributors_list.sh deleted file mode 100755 index 084bb5f..0000000 --- a/scripts/generate_contributors_list.sh +++ /dev/null @@ -1,53 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceDiscovery open source project -## -## Copyright (c) 2020 Apple Inc. and the SwiftServiceDiscovery project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftNIO open source project -## -## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftNIO project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu -here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -contributors=$( cd "$here"/.. && git shortlog -es | cut -f2 | sed 's/^/- /' ) - -cat > "$here/../CONTRIBUTORS.txt" <<- EOF - For the purpose of tracking copyright, this is the list of individuals and - organizations who have contributed source code to the Swift Service Discovery. - - For employees of an organization/company where the copyright of work done - by employees of that company is held by the company itself, only the company - needs to be listed here. - - ## COPYRIGHT HOLDERS - - - Apple Inc. (all contributors with '@apple.com') - - ### Contributors - - $contributors - - **Updating this list** - - Please do not edit this file manually. It is generated using \`./scripts/generate_contributors_list.sh\`. If a name is misspelled or appearing multiple times: add an entry in \`./.mailmap\` -EOF diff --git a/scripts/generate_docs.sh b/scripts/generate_docs.sh deleted file mode 100755 index f422ce0..0000000 --- a/scripts/generate_docs.sh +++ /dev/null @@ -1,126 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceDiscovery open source project -## -## Copyright (c) 2020 Apple Inc. and the SwiftServiceDiscovery project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftNIO open source project -## -## Copyright (c) 2017-2018 Apple Inc. and the SwiftNIO project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftNIO project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -ex - -my_path="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" -root_path="$my_path/.." -version=$(git describe --abbrev=0 --tags || echo "0.0.0") -modules=(ServiceDiscovery) - -if [[ "$(uname -s)" == "Linux" ]]; then - # build code if required - if [[ ! -d "$root_path/.build/x86_64-unknown-linux" ]]; then - swift build - fi - # setup source-kitten if required - source_kitten_source_path="$root_path/.SourceKitten" - if [[ ! -d "$source_kitten_source_path" ]]; then - git clone https://github.com/jpsim/SourceKitten.git "$source_kitten_source_path" - fi - source_kitten_path="$source_kitten_source_path/.build/x86_64-unknown-linux/debug" - if [[ ! -d "$source_kitten_path" ]]; then - rm -rf "$source_kitten_source_path/.swift-version" - cd "$source_kitten_source_path" && swift build && cd "$root_path" - fi - # generate - mkdir -p "$root_path/.build/sourcekitten" - for module in "${modules[@]}"; do - if [[ ! -f "$root_path/.build/sourcekitten/$module.json" ]]; then - "$source_kitten_path/sourcekitten" doc --spm-module $module > "$root_path/.build/sourcekitten/$module.json" - fi - done -fi - -[[ -d docs/$version ]] || mkdir -p docs/$version -[[ -d swift-service-discovery.xcodeproj ]] || swift package generate-xcodeproj - -# run jazzy -if ! command -v jazzy > /dev/null; then - gem install jazzy --no-ri --no-rdoc -fi - -jazzy_dir="$root_path/.build/jazzy" -rm -rf "$jazzy_dir" -mkdir -p "$jazzy_dir" - -module_switcher="$jazzy_dir/README.md" -jazzy_args=(--clean - --author 'SwiftServiceDiscovery team' - --readme "$module_switcher" - --author_url https://github.com/apple/swift-service-discovery - --github_url https://github.com/apple/swift-service-discovery - --github-file-prefix https://github.com/apple/swift-service-discovery/tree/$version - --theme fullwidth - --xcodebuild-arguments -scheme,swift-service-discovery-Package) -cat > "$module_switcher" <<"EOF" -# SwiftServiceDiscovery Docs - -SwiftServiceDiscovery is a Swift service discovery API package. - -To get started with SwiftServiceDiscovery, [`import ServiceDiscovery`](../ServiceDiscovery/index.html). The -most important type is [`ServiceDiscovery`](https://apple.github.io/swift-service-discovery/docs/current/ServiceDiscovery/Protocols/ServiceDiscovery.html) -which you can use for service discovery. -EOF - -for module in "${modules[@]}"; do - args=("${jazzy_args[@]}" --output "$jazzy_dir/docs/$version/$module" --docset-path "$jazzy_dir/docset/$version/$module" - --module "$module" --module-version $version - --root-url "https://apple.github.io/swift-service-discovery/docs/$version/$module/") - if [[ -f "$root_path/.build/sourcekitten/$module.json" ]]; then - args+=(--sourcekitten-sourcefile "$root_path/.build/sourcekitten/$module.json") - fi - jazzy "${args[@]}" -done - -# push to github pages -if [[ $PUSH == true ]]; then - BRANCH_NAME=$(git rev-parse --abbrev-ref HEAD) - GIT_AUTHOR=$(git --no-pager show -s --format='%an <%ae>' HEAD) - git fetch origin +gh-pages:gh-pages - git checkout gh-pages - rm -rf "docs/$version" - rm -rf "docs/current" - cp -r "$jazzy_dir/docs/$version" docs/ - cp -r "docs/$version" docs/current - git add --all docs - echo '' > index.html - git add index.html - touch .nojekyll - git add .nojekyll - changes=$(git diff-index --name-only HEAD) - if [[ -n "$changes" ]]; then - echo -e "changes detected\n$changes" - git commit --author="$GIT_AUTHOR" -m "publish $version docs" - git push origin gh-pages - else - echo "no changes detected" - fi - git checkout -f $BRANCH_NAME -fi diff --git a/scripts/preview_docc.sh b/scripts/preview_docc.sh deleted file mode 100755 index af355bc..0000000 --- a/scripts/preview_docc.sh +++ /dev/null @@ -1,30 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceDiscovery open source project -## -## Copyright (c) 2022 Apple Inc. and the SwiftServiceDiscovery project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -##===----------------------------------------------------------------------===## -## -## This source file is part of the Swift Distributed Actors open source project -## -## Copyright (c) 2018-2019 Apple Inc. and the Swift Distributed Actors project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.md for the list of Swift Distributed Actors project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -swift package --disable-sandbox preview-documentation --target $1 diff --git a/scripts/soundness.sh b/scripts/soundness.sh deleted file mode 100755 index f9d7221..0000000 --- a/scripts/soundness.sh +++ /dev/null @@ -1,143 +0,0 @@ -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceDiscovery open source project -## -## Copyright (c) 2020-2024 Apple Inc. and the SwiftServiceDiscovery project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## - -set -eu -here="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -function replace_acceptable_years() { - # this needs to replace all acceptable forms with 'YEARS' - sed -e 's/20[12][90123]-202[01234]/YEARS/' -e 's/2019/YEARS/' -e 's/202[01234]/YEARS/' -} - -printf "=> Checking for unacceptable language... " -# This greps for unacceptable terminology. The square bracket[s] are so that -# "git grep" doesn't find the lines that greps :). -unacceptable_terms=( - -e blacklis[t] - -e whitelis[t] - -e slav[e] - -e sanit[y] -) -if git grep --color=never -i "${unacceptable_terms[@]}" > /dev/null; then - printf "\033[0;31mUnacceptable language found.\033[0m\n" - git grep -i "${unacceptable_terms[@]}" - exit 1 -fi -printf "\033[0;32mokay.\033[0m\n" - -printf "=> Checking format... " -FIRST_OUT="$(git status --porcelain)" -swiftformat . > /dev/null 2>&1 -SECOND_OUT="$(git status --porcelain)" -if [[ "$FIRST_OUT" != "$SECOND_OUT" ]]; then - printf "\033[0;31mformatting issues!\033[0m\n" - git --no-pager diff - exit 1 -else - printf "\033[0;32mokay.\033[0m\n" -fi - -printf "=> Checking license headers...\n" -tmp=$(mktemp /tmp/.swift-service-discovery-soundness_XXXXXX) - -for language in swift-or-c bash dtrace; do - printf " * checking $language... " - declare -a matching_files - declare -a exceptions - expections=( ) - matching_files=( -name '*' ) - case "$language" in - swift-or-c) - exceptions=( -name Package.swift -o -name 'Package@*.swift' ) - matching_files=( -name '*.swift' -o -name '*.c' -o -name '*.h' ) - cat > "$tmp" <<"EOF" -//===----------------------------------------------------------------------===// -// -// This source file is part of the SwiftServiceDiscovery open source project -// -// Copyright (c) YEARS Apple Inc. and the SwiftServiceDiscovery project authors -// Licensed under Apache License v2.0 -// -// See LICENSE.txt for license information -// See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -// -// SPDX-License-Identifier: Apache-2.0 -// -//===----------------------------------------------------------------------===// -EOF - ;; - bash) - matching_files=( -name '*.sh' ) - cat > "$tmp" <<"EOF" -#!/bin/bash -##===----------------------------------------------------------------------===## -## -## This source file is part of the SwiftServiceDiscovery open source project -## -## Copyright (c) YEARS Apple Inc. and the SwiftServiceDiscovery project authors -## Licensed under Apache License v2.0 -## -## See LICENSE.txt for license information -## See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors -## -## SPDX-License-Identifier: Apache-2.0 -## -##===----------------------------------------------------------------------===## -EOF - ;; - dtrace) - matching_files=( -name '*.d' ) - cat > "$tmp" <<"EOF" -#!/usr/sbin/dtrace -q -s -/*===----------------------------------------------------------------------===* - * - * This source file is part of the SwiftServiceDiscovery open source project - * - * Copyright (c) YEARS Apple Inc. and the SwiftServiceDiscovery project authors - * Licensed under Apache License v2.0 - * - * See LICENSE.txt for license information - * See CONTRIBUTORS.txt for the list of SwiftServiceDiscovery project authors - * - * SPDX-License-Identifier: Apache-2.0 - * - *===----------------------------------------------------------------------===*/ -EOF - ;; - *) - echo >&2 "ERROR: unknown language '$language'" - ;; - esac - - expected_lines=$(cat "$tmp" | wc -l) - expected_sha=$(cat "$tmp" | shasum) - - ( - cd "$here/.." - find . \ - \( \! -path './.build/*' -a \ - \( "${matching_files[@]}" \) -a \ - \( \! \( "${exceptions[@]}" \) \) \) | while read line; do - if [[ "$(cat "$line" | replace_acceptable_years | head -n $expected_lines | shasum)" != "$expected_sha" ]]; then - printf "\033[0;31mmissing headers in file '$line'!\033[0m\n" - diff -u <(cat "$line" | replace_acceptable_years | head -n $expected_lines) "$tmp" - exit 1 - fi - done - printf "\033[0;32mokay.\033[0m\n" - ) -done - -rm "$tmp"