Skip to content

Commit

Permalink
Merge 4033393 into 2538480
Browse files Browse the repository at this point in the history
  • Loading branch information
Kalzem committed Jul 5, 2020
2 parents 2538480 + 4033393 commit 78def4e
Show file tree
Hide file tree
Showing 4 changed files with 271 additions and 1 deletion.
18 changes: 18 additions & 0 deletions Queuer.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,13 @@
49E803CB21868A310001B47A /* OperationStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E803CA21868A310001B47A /* OperationStateTests.swift */; };
49E803CC21868A310001B47A /* OperationStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E803CA21868A310001B47A /* OperationStateTests.swift */; };
49E803CD21868A310001B47A /* OperationStateTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E803CA21868A310001B47A /* OperationStateTests.swift */; };
E7A1AEF9220CADB400D40A92 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEF8220CADB400D40A92 /* GroupOperation.swift */; };
E7A1AEFA220CADB400D40A92 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEF8220CADB400D40A92 /* GroupOperation.swift */; };
E7A1AEFB220CADB400D40A92 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEF8220CADB400D40A92 /* GroupOperation.swift */; };
E7A1AEFC220CADB400D40A92 /* GroupOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEF8220CADB400D40A92 /* GroupOperation.swift */; };
E7A1AEFE220CADC900D40A92 /* GroupOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEFD220CADC900D40A92 /* GroupOperationTests.swift */; };
E7A1AEFF220CADC900D40A92 /* GroupOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEFD220CADC900D40A92 /* GroupOperationTests.swift */; };
E7A1AF00220CADC900D40A92 /* GroupOperationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = E7A1AEFD220CADC900D40A92 /* GroupOperationTests.swift */; };
/* End PBXBuildFile section */

/* Begin PBXContainerItemProxy section */
Expand Down Expand Up @@ -105,6 +112,8 @@
49C7BCC11F26938F00F4FFBC /* Queuer.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Queuer.h; sourceTree = "<group>"; };
49E803C5217FE8E80001B47A /* OperationState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OperationState.swift; sourceTree = "<group>"; };
49E803CA21868A310001B47A /* OperationStateTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationStateTests.swift; sourceTree = "<group>"; };
E7A1AEF8220CADB400D40A92 /* GroupOperation.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupOperation.swift; sourceTree = "<group>"; };
E7A1AEFD220CADC900D40A92 /* GroupOperationTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupOperationTests.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */

/* Begin PBXFrameworksBuildPhase section */
Expand Down Expand Up @@ -199,6 +208,7 @@
isa = PBXGroup;
children = (
49C7BCAF1F26938F00F4FFBC /* ConcurrentOperationTests.swift */,
E7A1AEFD220CADC900D40A92 /* GroupOperationTests.swift */,
49E803CA21868A310001B47A /* OperationStateTests.swift */,
49C7BCB01F26938F00F4FFBC /* QueuerTests.swift */,
4946BDFA214D5C0100FAFC84 /* SchedulerTests.swift */,
Expand All @@ -222,6 +232,7 @@
isa = PBXGroup;
children = (
49C7BCBB1F26938F00F4FFBC /* ConcurrentOperation.swift */,
E7A1AEF8220CADB400D40A92 /* GroupOperation.swift */,
49E803C5217FE8E80001B47A /* OperationState.swift */,
49C7BCBC1F26938F00F4FFBC /* Queuer.swift */,
4946BDF5214D586900FAFC84 /* Scheduler.swift */,
Expand Down Expand Up @@ -527,6 +538,7 @@
49C7BCE41F26939A00F4FFBC /* Semaphore.swift in Sources */,
49C7BCE11F26939A00F4FFBC /* ConcurrentOperation.swift in Sources */,
4946BDF7214D586900FAFC84 /* Scheduler.swift in Sources */,
E7A1AEFA220CADB400D40A92 /* GroupOperation.swift in Sources */,
49C7BCE51F26939A00F4FFBC /* SynchronousOperation.swift in Sources */,
49E803C7217FE8E80001B47A /* OperationState.swift in Sources */,
);
Expand All @@ -536,6 +548,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E7A1AEFF220CADC900D40A92 /* GroupOperationTests.swift in Sources */,
49C7BCEE1F2693A000F4FFBC /* QueuerTests.swift in Sources */,
49E803CC21868A310001B47A /* OperationStateTests.swift in Sources */,
49C7BCF01F2693A000F4FFBC /* SemaphoreTests.swift in Sources */,
Expand All @@ -553,6 +566,7 @@
49C7BCD21F26938F00F4FFBC /* SynchronousOperation.swift in Sources */,
49C7BCCF1F26938F00F4FFBC /* Queuer.swift in Sources */,
4946BDF6214D586900FAFC84 /* Scheduler.swift in Sources */,
E7A1AEF9220CADB400D40A92 /* GroupOperation.swift in Sources */,
49C7BCD11F26938F00F4FFBC /* Semaphore.swift in Sources */,
49E803C6217FE8E80001B47A /* OperationState.swift in Sources */,
);
Expand All @@ -562,6 +576,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E7A1AEFE220CADC900D40A92 /* GroupOperationTests.swift in Sources */,
49C7BCE81F26939F00F4FFBC /* QueuerTests.swift in Sources */,
49E803CB21868A310001B47A /* OperationStateTests.swift in Sources */,
49C7BCEA1F26939F00F4FFBC /* SemaphoreTests.swift in Sources */,
Expand All @@ -579,6 +594,7 @@
49C7BCD81F26939900F4FFBC /* Semaphore.swift in Sources */,
49C7BCD51F26939900F4FFBC /* ConcurrentOperation.swift in Sources */,
4946BDF9214D586900FAFC84 /* Scheduler.swift in Sources */,
E7A1AEFC220CADB400D40A92 /* GroupOperation.swift in Sources */,
49C7BCD91F26939900F4FFBC /* SynchronousOperation.swift in Sources */,
49E803C9217FE8E80001B47A /* OperationState.swift in Sources */,
);
Expand All @@ -592,6 +608,7 @@
49C7BCDE1F26939A00F4FFBC /* Semaphore.swift in Sources */,
49C7BCDB1F26939A00F4FFBC /* ConcurrentOperation.swift in Sources */,
4946BDF8214D586900FAFC84 /* Scheduler.swift in Sources */,
E7A1AEFB220CADB400D40A92 /* GroupOperation.swift in Sources */,
49C7BCDF1F26939A00F4FFBC /* SynchronousOperation.swift in Sources */,
49E803C8217FE8E80001B47A /* OperationState.swift in Sources */,
);
Expand All @@ -601,6 +618,7 @@
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
files = (
E7A1AF00220CADC900D40A92 /* GroupOperationTests.swift in Sources */,
49C7BCF41F2693A100F4FFBC /* QueuerTests.swift in Sources */,
49E803CD21868A310001B47A /* OperationStateTests.swift in Sources */,
49C7BCF61F2693A100F4FFBC /* SemaphoreTests.swift in Sources */,
Expand Down
2 changes: 1 addition & 1 deletion Sources/Queuer/ConcurrentOperation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ open class ConcurrentOperation: Operation {
open var manualRetry = false

/// Specify if the `Operation` should retry another time.
private var shouldRetry = true
internal var shouldRetry = true

/// Creates the `Operation` with an execution block.
///
Expand Down
63 changes: 63 additions & 0 deletions Sources/Queuer/GroupOperation.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
//
// ConcurrentOperation.swift
// Queuer
//
// MIT License
//
// Copyright (c) 2017 - 2020 Fabrizio Brancati
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import Foundation

open class GroupOperation: ConcurrentOperation {
/// Private `OperationQueue` instance.
private let queue = OperationQueue()

/// List of `ConcurrentOperation` that should be run in this `GroupOperation`.
public var operations: [ConcurrentOperation] = []

/// Flag to know if all `ConcurrentOperation` of this `GroupOperation` were successful.
public var allOperationsSucceeded: Bool {
return operations.first(where: { $0.success == false }) == nil
}

/// Creates the `GroupOperation` with a completion handler.
///
/// - Parameters:
/// - operations: Array of ConcurrentOperation to be executed.
/// - completionHandler: Block that will be executed once all operations are over.
public init(_ operations: [ConcurrentOperation], completionBlock: (() -> Void)? = nil) {
super.init()
self.operations = operations
self.completionBlock = completionBlock
}

/// A `GroupOperation` shouldn't be able to retry itself. It should be the responsability of its operations to retry themselves.
@available(*, obsoleted: 1.0, message: "Use retry on the children operations directly")
open override func retry() {}

/// Execute the `Operation`
/// The execution of a `GroupOperation` will always be considered successful.
/// Use the variable `allOperationsSucceeded` to know if an error occured on an operation in the Group.
open override func execute() {
queue.addOperations(operations, waitUntilFinished: true)
finish(success: true)
}
}
189 changes: 189 additions & 0 deletions Tests/QueuerTests/GroupOperationTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,189 @@
//
// ConcurrentOperation.swift
// Queuer
//
// MIT License
//
// Copyright (c) 2017 - 2020 Fabrizio Brancati
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all
// copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
// SOFTWARE.

import XCTest

@testable import Queuer
import XCTest

internal class GroupOperationTests: XCTestCase {
func testGroupOperations() {
var order: [String] = []
let testExpectation = expectation(description: "GroupOperations")
let queue = Queuer(name: "Group Operations")

let groupOperation1 = GroupOperation([
ConcurrentOperation() { _ in
Thread.sleep(forTimeInterval: 1)
order.append("1")
},
ConcurrentOperation() { _ in
order.append("2")
}
]) {
order.append("3")
}

let groupOperation2 = GroupOperation([
ConcurrentOperation() { _ in
order.append("4")
},
ConcurrentOperation() { _ in
Thread.sleep(forTimeInterval: 1)
order.append("5")
}
]) {
order.append("6")
}

let groupOperation3 = ConcurrentOperation() { _ in
Thread.sleep(forTimeInterval: 1)
order.append("7")
}

queue.addChainedOperations([groupOperation1, groupOperation2, groupOperation3]) {
testExpectation.fulfill()
}

waitForExpectations(timeout: 5) { error in
XCTAssertTrue(groupOperation1.allOperationsSucceeded)
XCTAssertNil(error)
XCTAssertEqual(order, ["2", "1", "3", "4", "5", "6", "7"])
}
}

func testGroupOperationsWithInnerChainedRetry() {
var order: [String] = []
let testExpectation = expectation(description: "GroupOperationsWithInnerChainedRetry")
let queue = Queuer(name: "Group Operations Chained Retry")

let groupOperation1 = GroupOperation([
ConcurrentOperation() { _ in
order.append("1")
},
ConcurrentOperation() { operation in
Thread.sleep(forTimeInterval: 1)
order.append("2")
operation.success = false
}
])

let groupOperation2 = ConcurrentOperation() { _ in
order.append("3")
}

queue.addChainedOperations([groupOperation1, groupOperation2]) {
testExpectation.fulfill()
}

waitForExpectations(timeout: 8) { error in
XCTAssertNil(error)
XCTAssertEqual(order, ["1", "2", "2", "2", "3"])
}
}

func testGroupOperationsWithCancelledInnerChainedRetry() {
let queue = Queuer(name: "GroupOperationsWithCancelledInnerChainedRetry")
let testExpectation = expectation(description: "Group Operations Cancelled Inner Chained Retry")
var order: [String] = []

let groupOperation1 = GroupOperation([
ConcurrentOperation() { operation in
Thread.sleep(forTimeInterval: 1)
order.append("1")
operation.success = false
},
ConcurrentOperation() { operation in
operation.cancel()
guard !operation.isCancelled else {
return
}
order.append("2")
operation.success = false
}
])

let groupOperation2 = ConcurrentOperation() { _ in
order.append("3")
}

queue.addChainedOperations([groupOperation1, groupOperation2]) {
testExpectation.fulfill()
}

waitForExpectations(timeout: 6) { error in
XCTAssertNil(error)
XCTAssertEqual(order, ["1", "1", "1", "3"])
}
}

func testGroupOperationsWithInnerChainedManualRetry() {
let queue = Queuer(name: "GroupOperationsWithInnerChainedManualRetry")
let testExpectation = expectation(description: "Group Operations Inner Chained Manual Retry")
var order: [String] = []

let concurrentOperation1 = ConcurrentOperation() { operation in
order.append("1")
operation.success = false
}
concurrentOperation1.manualRetry = true

let concurrentOperation2 = ConcurrentOperation() { operation in
Thread.sleep(forTimeInterval: 1)
order.append("2")
operation.success = false
}
concurrentOperation2.manualRetry = true

let groupOperation1 = GroupOperation([concurrentOperation1, concurrentOperation2])

let groupOperation2 = ConcurrentOperation() { _ in
order.append("3")
}

queue.addChainedOperations([groupOperation1, groupOperation2]) {
testExpectation.fulfill()
}

DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(3)) {
concurrentOperation1.retry()
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(4)) {
concurrentOperation2.retry()
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(5)) {
concurrentOperation2.retry()
}
DispatchQueue.main.asyncAfter(deadline: .now() + .seconds(6)) {
concurrentOperation1.retry()
}

waitForExpectations(timeout: 8) { error in
XCTAssertNil(error)
XCTAssertEqual(order, ["1", "2", "1", "2", "2", "1", "3"])
}
}
}

0 comments on commit 78def4e

Please sign in to comment.