diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 74704e2..363927e 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -2,23 +2,22 @@ name: Test on: push: - branches: [ master ] + branches: [ main ] pull_request: - branches: [ master ] + branches: [ main ] jobs: - linux-build: - name: Build and test on ${{ matrix.os }} - runs-on: ${{ matrix.os }} - strategy: - matrix: - os: [ubuntu-latest] + formatlint: + name: Format linting + runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 - - uses: swift-actions/setup-swift@v1 - - name: Build - run: swift build - - name: Run tests - run: swift test + - uses: actions/checkout@v3 + - uses: sinoru/actions-setup-swift@v2 + with: + swift-version: '5.6.1' + - name: GitHub Action for SwiftFormat + uses: CassiusPacheco/action-swiftformat@v0.1.0 + with: + swiftformat-version: '0.49.17' macos-build: name: Build and test on ${{ matrix.os }} runs-on: ${{ matrix.os }} @@ -34,3 +33,18 @@ jobs: run: swift build - name: Run tests run: swift test + linux-build: + name: Build and test ${{ matrix.swift }} on ${{ matrix.os }} + runs-on: ${{ matrix.os }} + strategy: + matrix: + os: [ubuntu-latest] + swift: ["5.4", "5.5", "5.6"] + steps: + - uses: swift-actions/setup-swift@v1 + with: + swift-version: ${{ matrix.swift }} + - uses: actions/checkout@v2 + - name: Test + run: swift test + diff --git a/.swiftformat b/.swiftformat new file mode 100644 index 0000000..74ed979 --- /dev/null +++ b/.swiftformat @@ -0,0 +1,7 @@ +--maxwidth 100 +--semicolons never +--xcodeindentation enabled +--wraparguments before-first +--wrapcollections before-first +--wrapconditions before-first +--wrapparameters before-first diff --git a/README.md b/README.md index c40de24..7cf54ff 100644 --- a/README.md +++ b/README.md @@ -290,6 +290,13 @@ to reproduce your problem. 💥 When creating a pull request, please adhere to the current coding style where possible, and create tests with your code so it keeps providing an awesome test coverage level 💪 +This repo uses [SwiftFormat](https://github.com/nicklockwood/SwiftFormat), and includes lint checks to enforce these formatting standards. +To format your code, install `swiftformat` and run: + +```bash +swiftformat . +``` + ## Acknowledgements 👏 This library is entirely a Swift version of Facebooks [DataLoader](https://github.com/facebook/dataloader). diff --git a/Sources/DataLoader/DataLoader.swift b/Sources/DataLoader/DataLoader.swift index 6618f5f..dce6ce5 100644 --- a/Sources/DataLoader/DataLoader.swift +++ b/Sources/DataLoader/DataLoader.swift @@ -6,8 +6,9 @@ public enum DataLoaderFutureValue { case failure(Error) } -public typealias BatchLoadFunction = (_ keys: [Key]) throws -> EventLoopFuture<[DataLoaderFutureValue]> -private typealias LoaderQueue = Array<(key: Key, promise: EventLoopPromise)> +public typealias BatchLoadFunction = (_ keys: [Key]) throws + -> EventLoopFuture<[DataLoaderFutureValue]> +private typealias LoaderQueue = [(key: Key, promise: EventLoopPromise)] /// DataLoader creates a public API for loading data from a particular /// data back-end with unique keys such as the id column of a SQL table @@ -17,14 +18,13 @@ private typealias LoaderQueue = Array<(key: Key, promise: EventLoopP /// when used in long-lived applications or those which serve many users /// with different access permissions and consider creating a new instance /// per data request. -final public class DataLoader { - +public final class DataLoader { private let batchLoadFunction: BatchLoadFunction private let options: DataLoaderOptions private var cache = [Key: EventLoopFuture]() private var queue = LoaderQueue() - + private var dispatchScheduled = false private let lock = Lock() @@ -39,7 +39,7 @@ final public class DataLoader { /// Loads a key, returning an `EventLoopFuture` for the value represented by that key. public func load(key: Key, on eventLoopGroup: EventLoopGroup) throws -> EventLoopFuture { let cacheKey = options.cacheKeyFunction?(key) ?? key - + return lock.withLock { if options.cachingEnabled, let cachedFuture = cache[cacheKey] { return cachedFuture @@ -57,14 +57,18 @@ final public class DataLoader { } } else { do { - _ = try batchLoadFunction([key]).map { results in + _ = try batchLoadFunction([key]).map { results in if results.isEmpty { - promise.fail(DataLoaderError.noValueForKey("Did not return value for key: \(key)")) + promise + .fail( + DataLoaderError + .noValueForKey("Did not return value for key: \(key)") + ) } else { let result = results[0] switch result { - case .success(let value): promise.succeed(value) - case .failure(let error): promise.fail(error) + case let .success(value): promise.succeed(value) + case let .failure(error): promise.fail(error) } } } @@ -82,7 +86,7 @@ final public class DataLoader { return future } } - + /// Loads multiple keys, promising an array of values: /// /// ``` @@ -97,14 +101,17 @@ final public class DataLoader { /// myLoader.load(key: "b", on: eventLoopGroup) /// ].flatten(on: eventLoopGroup).wait() /// ``` - public func loadMany(keys: [Key], on eventLoopGroup: EventLoopGroup) throws -> EventLoopFuture<[Value]> { + public func loadMany( + keys: [Key], + on eventLoopGroup: EventLoopGroup + ) throws -> EventLoopFuture<[Value]> { guard !keys.isEmpty else { return eventLoopGroup.next().makeSucceededFuture([]) } let futures = try keys.map { try load(key: $0, on: eventLoopGroup) } return EventLoopFuture.whenAllSucceed(futures, on: eventLoopGroup.next()) } - + /// Clears the value at `key` from the cache, if it exists. Returns itself for /// method chaining. @discardableResult @@ -115,7 +122,7 @@ final public class DataLoader { } return self } - + /// Clears the entire cache. To be used when some event results in unknown /// invalidations across this particular `DataLoader`. Returns itself for /// method chaining. @@ -130,9 +137,13 @@ final public class DataLoader { /// Adds the provied key and value to the cache. If the key already exists, no /// change is made. Returns itself for method chaining. @discardableResult - public func prime(key: Key, value: Value, on eventLoop: EventLoopGroup) -> DataLoader { + public func prime( + key: Key, + value: Value, + on eventLoop: EventLoopGroup + ) -> DataLoader { let cacheKey = options.cacheKeyFunction?(key) ?? key - + lock.withLockVoid { if cache[cacheKey] == nil { let promise: EventLoopPromise = eventLoop.next().makePromise() @@ -160,27 +171,27 @@ final public class DataLoader { dispatchScheduled = false } } - + guard batch.count > 0 else { return () } // If a maxBatchSize was provided and the queue is longer, then segment the // queue into multiple batches, otherwise treat the queue as a single batch. - if let maxBatchSize = options.maxBatchSize, maxBatchSize > 0 && maxBatchSize < batch.count { - for i in 0...(batch.count / maxBatchSize) { + if let maxBatchSize = options.maxBatchSize, maxBatchSize > 0, maxBatchSize < batch.count { + for i in 0 ... (batch.count / maxBatchSize) { let startIndex = i * maxBatchSize let endIndex = (i + 1) * maxBatchSize - let slicedBatch = batch[startIndex..) throws { - let keys = batch.map { $0.key } + let keys = batch.map(\.key) if keys.isEmpty { return @@ -191,22 +202,25 @@ final public class DataLoader { do { _ = try batchLoadFunction(keys).flatMapThrowing { values in if values.count != keys.count { - throw DataLoaderError.typeError("The function did not return an array of the same length as the array of keys. \nKeys count: \(keys.count)\nValues count: \(values.count)") + throw DataLoaderError + .typeError( + "The function did not return an array of the same length as the array of keys. \nKeys count: \(keys.count)\nValues count: \(values.count)" + ) } for entry in batch.enumerated() { let result = values[entry.offset] switch result { - case .failure(let error): entry.element.promise.fail(error) - case .success(let value): entry.element.promise.succeed(value) + case let .failure(error): entry.element.promise.fail(error) + case let .success(value): entry.element.promise.succeed(value) } } }.recover { error in self.failedExecution(batch: batch, error: error) } } catch { - self.failedExecution(batch: batch, error: error) + failedExecution(batch: batch, error: error) } } @@ -220,48 +234,49 @@ final public class DataLoader { #if compiler(>=5.5) && canImport(_Concurrency) -/// Batch load function using async await -public typealias ConcurrentBatchLoadFunction = @Sendable (_ keys: [Key]) async throws -> [DataLoaderFutureValue] + /// Batch load function using async await + public typealias ConcurrentBatchLoadFunction = + @Sendable (_ keys: [Key]) async throws -> [DataLoaderFutureValue] -public extension DataLoader { - @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) - convenience init( - on eventLoop: EventLoop, - options: DataLoaderOptions = DataLoaderOptions(), - throwing asyncThrowingLoadFunction: @escaping ConcurrentBatchLoadFunction - ) { - self.init(options: options, batchLoadFunction: { keys in - let promise = eventLoop.next().makePromise(of: [DataLoaderFutureValue].self) - promise.completeWithTask { - try await asyncThrowingLoadFunction(keys) - } - return promise.futureResult - }) - } + public extension DataLoader { + @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) + convenience init( + on eventLoop: EventLoop, + options: DataLoaderOptions = DataLoaderOptions(), + throwing asyncThrowingLoadFunction: @escaping ConcurrentBatchLoadFunction + ) { + self.init(options: options, batchLoadFunction: { keys in + let promise = eventLoop.next().makePromise(of: [DataLoaderFutureValue].self) + promise.completeWithTask { + try await asyncThrowingLoadFunction(keys) + } + return promise.futureResult + }) + } - /// Asynchronously loads a key, returning the value represented by that key. - @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) - func load(key: Key, on eventLoopGroup: EventLoopGroup) async throws -> Value { - try await load(key: key, on: eventLoopGroup).get() - } + /// Asynchronously loads a key, returning the value represented by that key. + @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) + func load(key: Key, on eventLoopGroup: EventLoopGroup) async throws -> Value { + try await load(key: key, on: eventLoopGroup).get() + } - /// Asynchronously loads multiple keys, promising an array of values: - /// - /// ``` - /// let aAndB = try await myLoader.loadMany(keys: [ "a", "b" ], on: eventLoopGroup) - /// ``` - /// - /// This is equivalent to the more verbose: - /// - /// ``` - /// async let a = myLoader.load(key: "a", on: eventLoopGroup) - /// async let b = myLoader.load(key: "b", on: eventLoopGroup) - /// let aAndB = try await a + b - /// ``` - @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) - func loadMany(keys: [Key], on eventLoopGroup: EventLoopGroup) async throws -> [Value] { - try await loadMany(keys: keys, on: eventLoopGroup).get() + /// Asynchronously loads multiple keys, promising an array of values: + /// + /// ``` + /// let aAndB = try await myLoader.loadMany(keys: [ "a", "b" ], on: eventLoopGroup) + /// ``` + /// + /// This is equivalent to the more verbose: + /// + /// ``` + /// async let a = myLoader.load(key: "a", on: eventLoopGroup) + /// async let b = myLoader.load(key: "b", on: eventLoopGroup) + /// let aAndB = try await a + b + /// ``` + @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) + func loadMany(keys: [Key], on eventLoopGroup: EventLoopGroup) async throws -> [Value] { + try await loadMany(keys: keys, on: eventLoopGroup).get() + } } -} -#endif \ No newline at end of file +#endif diff --git a/Sources/DataLoader/DataLoaderOptions.swift b/Sources/DataLoader/DataLoaderOptions.swift index 283f146..5351637 100644 --- a/Sources/DataLoader/DataLoaderOptions.swift +++ b/Sources/DataLoader/DataLoaderOptions.swift @@ -5,32 +5,33 @@ public struct DataLoaderOptions { /// `batchLoadFunction` with a single load key. This is /// equivalent to setting `maxBatchSize` to `1`. public let batchingEnabled: Bool - + /// Default `nil`. Limits the number of items that get passed in to the /// `batchLoadFn`. May be set to `1` to disable batching. public let maxBatchSize: Int? - + /// Default `true`. Set to `false` to disable memoization caching, creating a /// new `EventLoopFuture` and new key in the `batchLoadFunction` /// for every load of the same key. public let cachingEnabled: Bool - + /// Default `2ms`. Defines the period of time that the DataLoader should /// wait and collect its queue before executing. Faster times result /// in smaller batches quicker resolution, slower times result in larger /// batches but slower resolution. /// This is irrelevant if batching is disabled. public let executionPeriod: TimeAmount? - + /// Default `nil`. Produces cache key for a given load key. Useful /// when objects are keys and two objects should be considered equivalent. public let cacheKeyFunction: ((Key) -> Key)? - public init(batchingEnabled: Bool = true, - cachingEnabled: Bool = true, - maxBatchSize: Int? = nil, - executionPeriod: TimeAmount? = .milliseconds(2), - cacheKeyFunction: ((Key) -> Key)? = nil + public init( + batchingEnabled: Bool = true, + cachingEnabled: Bool = true, + maxBatchSize: Int? = nil, + executionPeriod: TimeAmount? = .milliseconds(2), + cacheKeyFunction: ((Key) -> Key)? = nil ) { self.batchingEnabled = batchingEnabled self.cachingEnabled = cachingEnabled diff --git a/Tests/DataLoaderTests/DataLoaderAbuseTests.swift b/Tests/DataLoaderTests/DataLoaderAbuseTests.swift index 73f2ad5..04203fe 100644 --- a/Tests/DataLoaderTests/DataLoaderAbuseTests.swift +++ b/Tests/DataLoaderTests/DataLoaderAbuseTests.swift @@ -1,11 +1,10 @@ -import XCTest import NIO +import XCTest @testable import DataLoader /// Provides descriptive error messages for API abuse class DataLoaderAbuseTests: XCTestCase { - func testFuntionWithNoValues() throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { @@ -14,7 +13,7 @@ class DataLoaderAbuseTests: XCTestCase { let identityLoader = DataLoader( options: DataLoaderOptions(batchingEnabled: false) - ) { keys in + ) { _ in eventLoopGroup.next().makeSucceededFuture([]) } @@ -29,13 +28,16 @@ class DataLoaderAbuseTests: XCTestCase { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - let identityLoader = DataLoader() { keys in + let identityLoader = DataLoader() { _ in eventLoopGroup.next().makeSucceededFuture([]) } let value = try identityLoader.load(key: 1, on: eventLoopGroup) - XCTAssertThrowsError(try value.wait(), "The function did not return an array of the same length as the array of keys. \nKeys count: 1\nValues count: 0") + XCTAssertThrowsError( + try value.wait(), + "The function did not return an array of the same length as the array of keys. \nKeys count: 1\nValues count: 0" + ) } func testBatchFuntionWithSomeValues() throws { @@ -97,4 +99,4 @@ class DataLoaderAbuseTests: XCTestCase { } } -extension String: Error { } +extension String: Error {} diff --git a/Tests/DataLoaderTests/DataLoaderAsyncTests.swift b/Tests/DataLoaderTests/DataLoaderAsyncTests.swift index 1f67f05..9ecb41a 100644 --- a/Tests/DataLoaderTests/DataLoaderAsyncTests.swift +++ b/Tests/DataLoaderTests/DataLoaderAsyncTests.swift @@ -1,117 +1,115 @@ -import XCTest import NIO +import XCTest @testable import DataLoader #if compiler(>=5.5) && canImport(_Concurrency) -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -actor Concurrent { - var wrappedValue: T + @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) + actor Concurrent { + var wrappedValue: T - func nonmutating(_ action: (T) throws -> Returned) async rethrows -> Returned { - try action(wrappedValue) - } + func nonmutating(_ action: (T) throws -> Returned) async rethrows -> Returned { + try action(wrappedValue) + } - func mutating(_ action: (inout T) throws -> Returned) async rethrows -> Returned { - try action(&wrappedValue) - } + func mutating(_ action: (inout T) throws -> Returned) async rethrows -> Returned { + try action(&wrappedValue) + } - init(_ value: T) { - self.wrappedValue = value + init(_ value: T) { + wrappedValue = value + } } -} + /// Primary API + @available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) + final class DataLoaderAsyncTests: XCTestCase { + /// Builds a really really simple data loader with async await + func testReallyReallySimpleDataLoader() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { + XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) + } -/// Primary API -@available(macOS 12, iOS 15, watchOS 8, tvOS 15, *) -final class DataLoaderAsyncTests: XCTestCase { + let identityLoader = DataLoader( + on: eventLoopGroup.next(), + options: DataLoaderOptions(batchingEnabled: false) + ) { keys async in + let task = Task { + keys.map { DataLoaderFutureValue.success($0) } + } + return await task.value + } - /// Builds a really really simple data loader with async await - func testReallyReallySimpleDataLoader() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) + let value = try await identityLoader.load(key: 1, on: eventLoopGroup) + + XCTAssertEqual(value, 1) } - let identityLoader = DataLoader( - on: eventLoopGroup.next(), - options: DataLoaderOptions(batchingEnabled: false) - ) { keys async in - let task = Task { - keys.map { DataLoaderFutureValue.success($0) } + /// Supports loading multiple keys in one call + func testLoadingMultipleKeys() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { + XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - return await task.value - } - let value = try await identityLoader.load(key: 1, on: eventLoopGroup) + let identityLoader = DataLoader(on: eventLoopGroup.next()) { keys in + let task = Task { + keys.map { DataLoaderFutureValue.success($0) } + } + return await task.value + } - XCTAssertEqual(value, 1) - } + let values = try await identityLoader.loadMany(keys: [1, 2], on: eventLoopGroup) + + XCTAssertEqual(values, [1, 2]) - /// Supports loading multiple keys in one call - func testLoadingMultipleKeys() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) + let empty = try await identityLoader.loadMany(keys: [], on: eventLoopGroup) + + XCTAssertTrue(empty.isEmpty) } - let identityLoader = DataLoader(on: eventLoopGroup.next()) { keys in - let task = Task { - keys.map { DataLoaderFutureValue.success($0) } + // Batches multiple requests + func testMultipleRequests() async throws { + let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) + defer { + XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - return await task.value - } - let values = try await identityLoader.loadMany(keys: [1, 2], on: eventLoopGroup) + let loadCalls = Concurrent<[[Int]]>([]) + + let identityLoader = DataLoader( + on: eventLoopGroup.next(), + options: DataLoaderOptions( + batchingEnabled: true, + executionPeriod: nil + ) + ) { keys in + await loadCalls.mutating { $0.append(keys) } + let task = Task { + keys.map { DataLoaderFutureValue.success($0) } + } + return await task.value + } - XCTAssertEqual(values, [1,2]) + async let value1 = identityLoader.load(key: 1, on: eventLoopGroup) + async let value2 = identityLoader.load(key: 2, on: eventLoopGroup) - let empty = try await identityLoader.loadMany(keys: [], on: eventLoopGroup) + /// Have to wait for a split second because Tasks may not be executed before this statement + try await Task.sleep(nanoseconds: 500_000_000) - XCTAssertTrue(empty.isEmpty) - } + XCTAssertNoThrow(try identityLoader.execute()) - // Batches multiple requests - func testMultipleRequests() async throws { - let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) - defer { - XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) - } + let result1 = try await value1 + XCTAssertEqual(result1, 1) + let result2 = try await value2 + XCTAssertEqual(result2, 2) - let loadCalls = Concurrent<[[Int]]>([]) - - let identityLoader = DataLoader( - on: eventLoopGroup.next(), - options: DataLoaderOptions( - batchingEnabled: true, - executionPeriod: nil - ) - ) { keys in - await loadCalls.mutating { $0.append(keys) } - let task = Task { - keys.map { DataLoaderFutureValue.success($0) } - } - return await task.value + let calls = await loadCalls.wrappedValue + XCTAssertEqual(calls.count, 1) + XCTAssertEqual(calls.map { $0.sorted() }, [[1, 2]]) } - - async let value1 = identityLoader.load(key: 1, on: eventLoopGroup) - async let value2 = identityLoader.load(key: 2, on: eventLoopGroup) - - /// Have to wait for a split second because Tasks may not be executed before this statement - try await Task.sleep(nanoseconds: 500_000_000) - - XCTAssertNoThrow(try identityLoader.execute()) - - let result1 = try await value1 - XCTAssertEqual(result1, 1) - let result2 = try await value2 - XCTAssertEqual(result2, 2) - - let calls = await loadCalls.wrappedValue - XCTAssertEqual(calls.count, 1) - XCTAssertEqual(calls.map { $0.sorted() }, [[1, 2]]) } -} -#endif \ No newline at end of file +#endif diff --git a/Tests/DataLoaderTests/DataLoaderTests.swift b/Tests/DataLoaderTests/DataLoaderTests.swift index bba2927..69d8f02 100644 --- a/Tests/DataLoaderTests/DataLoaderTests.swift +++ b/Tests/DataLoaderTests/DataLoaderTests.swift @@ -1,11 +1,10 @@ -import XCTest import NIO +import XCTest @testable import DataLoader /// Primary API final class DataLoaderTests: XCTestCase { - /// Builds a really really simple data loader' func testReallyReallySimpleDataLoader() throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -41,7 +40,7 @@ final class DataLoaderTests: XCTestCase { let values = try identityLoader.loadMany(keys: [1, 2], on: eventLoopGroup).wait() - XCTAssertEqual(values, [1,2]) + XCTAssertEqual(values, [1, 2]) let empty = try identityLoader.loadMany(keys: [], on: eventLoopGroup).wait() @@ -71,13 +70,13 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: 1, on: eventLoopGroup) let value2 = try identityLoader.load(key: 2, on: eventLoopGroup) - + XCTAssertNoThrow(try identityLoader.execute()) XCTAssertEqual(try value1.wait(), 1) XCTAssertEqual(try value2.wait(), 2) - XCTAssertEqual(loadCalls, [[1,2]]) + XCTAssertEqual(loadCalls, [[1, 2]]) } /// Batches multiple requests with max batch sizes @@ -105,14 +104,14 @@ final class DataLoaderTests: XCTestCase { let value1 = try identityLoader.load(key: 1, on: eventLoopGroup) let value2 = try identityLoader.load(key: 2, on: eventLoopGroup) let value3 = try identityLoader.load(key: 3, on: eventLoopGroup) - + XCTAssertNoThrow(try identityLoader.execute()) - + XCTAssertEqual(try value1.wait(), 1) XCTAssertEqual(try value2.wait(), 2) XCTAssertEqual(try value3.wait(), 3) - XCTAssertEqual(loadCalls, [[1,2], [3]]) + XCTAssertEqual(loadCalls, [[1, 2], [3]]) } /// Coalesces identical requests @@ -184,7 +183,6 @@ final class DataLoaderTests: XCTestCase { let value6 = try identityLoader.load(key: "B", on: eventLoopGroup) let value7 = try identityLoader.load(key: "C", on: eventLoopGroup) - XCTAssertNoThrow(try identityLoader.execute()) XCTAssertTrue(try value5.wait() == "A") @@ -384,7 +382,7 @@ final class DataLoaderTests: XCTestCase { XCTAssertTrue(loadCalls == [["B"]]) } - + // Caches repeated requests, even if initiated asyncronously func testCacheConcurrency() throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) @@ -397,22 +395,22 @@ final class DataLoaderTests: XCTestCase { return eventLoopGroup.next().makeSucceededFuture(results) } - + // Populate values from two different dispatch queues, running asynchronously var value1: EventLoopFuture = eventLoopGroup.next().makeSucceededFuture("") var value2: EventLoopFuture = eventLoopGroup.next().makeSucceededFuture("") - DispatchQueue.init(label: "").async { + DispatchQueue(label: "").async { value1 = try! identityLoader.load(key: "A", on: eventLoopGroup) } - DispatchQueue.init(label: "").async { + DispatchQueue(label: "").async { value2 = try! identityLoader.load(key: "A", on: eventLoopGroup) } - + // Sleep for a few ms ensure that value1 & value2 are populated before continuing usleep(1000) - + XCTAssertNoThrow(try identityLoader.execute()) - + // Test that the futures themselves are equal (not just the value). XCTAssertEqual(value1, value2) } @@ -431,7 +429,7 @@ final class DataLoaderTests: XCTestCase { return eventLoopGroup.next().makeSucceededFuture(results) } - var value: String? = nil + var value: String? _ = try identityLoader.load(key: "A", on: eventLoopGroup).map { result in value = result } @@ -441,36 +439,36 @@ final class DataLoaderTests: XCTestCase { XCTAssertNotNil(value) } - + func testErrorResult() throws { let eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 1) defer { XCTAssertNoThrow(try eventLoopGroup.syncShutdownGracefully()) } - + let loaderErrorMessage = "TEST" - + // Test throwing loader without auto-executing let throwLoader = DataLoader( options: DataLoaderOptions(executionPeriod: nil) - ) { keys in + ) { _ in throw DataLoaderError.typeError(loaderErrorMessage) } - + let value = try throwLoader.load(key: 1, on: eventLoopGroup) XCTAssertNoThrow(try throwLoader.execute()) XCTAssertThrowsError( try value.wait(), loaderErrorMessage ) - + // Test throwing loader with auto-executing let throwLoaderAutoExecute = DataLoader( options: DataLoaderOptions() - ) { keys in + ) { _ in throw DataLoaderError.typeError(loaderErrorMessage) } - + XCTAssertThrowsError( try throwLoaderAutoExecute.load(key: 1, on: eventLoopGroup).wait(), loaderErrorMessage diff --git a/azure-pipelines.yml b/azure-pipelines.yml deleted file mode 100644 index 21c759b..0000000 --- a/azure-pipelines.yml +++ /dev/null @@ -1,56 +0,0 @@ -trigger: -- master - -jobs: -- job: linux - pool: - vmImage: 'Ubuntu 16.04' - strategy: - maxParallel: 10 - matrix: - swift500: - containerImage: apple/swift:5 - container: $[ variables['containerImage'] ] - steps: - - script: swift test --parallel - displayName: swift test - -- job: Xcode - pool: - vmImage: 'macOS 10.14' - strategy: - maxParallel: 10 - matrix: - xcode102: - DEVELOPER_DIR: /Applications/Xcode_10.2.app - steps: - - script: swift package generate-xcodeproj - displayName: Generate Xcode project file - - script: xcodebuild -version - displayName: xcodebuild -version - - script: > - set -o pipefail && - xcodebuild -project DataLoader.xcodeproj -scheme DataLoader-Package test | - xcpretty -r junit -o build/reports/xcodebuild.xml - displayName: xcodebuild test - - task: PublishTestResults@2 - inputs: - testRunner: JUnit - testResultsFiles: build/reports/** - condition: succeededOrFailed() - -- job: SwiftPM - pool: - vmImage: 'macOS 10.14' - strategy: - maxParallel: 10 - matrix: - xcode101: - DEVELOPER_DIR: /Applications/Xcode_10.2.app - steps: - - script: xcodebuild -version - displayName: xcodebuild -version - - script: > - set -o pipefail && - swift test --parallel - displayName: swift test